/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) Module
 * -----------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Draw tests
 *//*--------------------------------------------------------------------*/

#include "glsDrawTest.hpp"

#include "deRandom.h"
#include "deRandom.hpp"
#include "deMath.h"
#include "deStringUtil.hpp"
#include "deFloat16.h"
#include "deUniquePtr.hpp"
#include "deArrayUtil.hpp"

#include "tcuTestLog.hpp"
#include "tcuPixelFormat.hpp"
#include "tcuRGBA.hpp"
#include "tcuSurface.hpp"
#include "tcuVector.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuImageCompare.hpp"
#include "tcuFloat.hpp"
#include "tcuTextureUtil.hpp"

#include "gluContextInfo.hpp"
#include "gluPixelTransfer.hpp"
#include "gluCallLogWrapper.hpp"

#include "sglrContext.hpp"
#include "sglrReferenceContext.hpp"
#include "sglrGLContext.hpp"

#include "rrGenericVector.hpp"

#include <cstring>
#include <cmath>
#include <vector>
#include <sstream>
#include <limits>

#include "glwDefs.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gls
{
namespace
{

using tcu::TestLog;
using namespace glw; // GL types

const int MAX_RENDER_TARGET_SIZE = 512;

// Utils

static GLenum targetToGL (DrawTestSpec::Target target)
{
	static const GLenum targets[] =
	{
		GL_ELEMENT_ARRAY_BUFFER,	// TARGET_ELEMENT_ARRAY = 0,
		GL_ARRAY_BUFFER				// TARGET_ARRAY,
	};

	return de::getSizedArrayElement<DrawTestSpec::TARGET_LAST>(targets, (int)target);
}

static GLenum usageToGL (DrawTestSpec::Usage usage)
{
	static const GLenum usages[] =
	{
		GL_DYNAMIC_DRAW,	// USAGE_DYNAMIC_DRAW = 0,
		GL_STATIC_DRAW,		// USAGE_STATIC_DRAW,
		GL_STREAM_DRAW,		// USAGE_STREAM_DRAW,

		GL_STREAM_READ,		// USAGE_STREAM_READ,
		GL_STREAM_COPY,		// USAGE_STREAM_COPY,

		GL_STATIC_READ,		// USAGE_STATIC_READ,
		GL_STATIC_COPY,		// USAGE_STATIC_COPY,

		GL_DYNAMIC_READ,	// USAGE_DYNAMIC_READ,
		GL_DYNAMIC_COPY		// USAGE_DYNAMIC_COPY,
	};

	return de::getSizedArrayElement<DrawTestSpec::USAGE_LAST>(usages, (int)usage);
}

static GLenum inputTypeToGL (DrawTestSpec::InputType type)
{
	static const GLenum types[] =
	{
		GL_FLOAT,				// INPUTTYPE_FLOAT = 0,
		GL_FIXED,				// INPUTTYPE_FIXED,
		GL_DOUBLE,				// INPUTTYPE_DOUBLE
		GL_BYTE,				// INPUTTYPE_BYTE,
		GL_SHORT,				// INPUTTYPE_SHORT,
		GL_UNSIGNED_BYTE,		// INPUTTYPE_UNSIGNED_BYTE,
		GL_UNSIGNED_SHORT,		// INPUTTYPE_UNSIGNED_SHORT,

		GL_INT,					// INPUTTYPE_INT,
		GL_UNSIGNED_INT,		// INPUTTYPE_UNSIGNED_INT,
		GL_HALF_FLOAT,			// INPUTTYPE_HALF,
		GL_UNSIGNED_INT_2_10_10_10_REV, // INPUTTYPE_UNSIGNED_INT_2_10_10_10,
		GL_INT_2_10_10_10_REV			// INPUTTYPE_INT_2_10_10_10,
	};

	return de::getSizedArrayElement<DrawTestSpec::INPUTTYPE_LAST>(types, (int)type);
}

static std::string outputTypeToGLType (DrawTestSpec::OutputType type)
{
	static const char* types[] =
	{
		"float",		// OUTPUTTYPE_FLOAT = 0,
		"vec2",			// OUTPUTTYPE_VEC2,
		"vec3",			// OUTPUTTYPE_VEC3,
		"vec4",			// OUTPUTTYPE_VEC4,

		"int",			// OUTPUTTYPE_INT,
		"uint",			// OUTPUTTYPE_UINT,

		"ivec2",		// OUTPUTTYPE_IVEC2,
		"ivec3",		// OUTPUTTYPE_IVEC3,
		"ivec4",		// OUTPUTTYPE_IVEC4,

		"uvec2",		// OUTPUTTYPE_UVEC2,
		"uvec3",		// OUTPUTTYPE_UVEC3,
		"uvec4",		// OUTPUTTYPE_UVEC4,
	};

	return de::getSizedArrayElement<DrawTestSpec::OUTPUTTYPE_LAST>(types, (int)type);
}

static GLenum primitiveToGL (DrawTestSpec::Primitive primitive)
{
	static const GLenum primitives[] =
	{
		GL_POINTS,						// PRIMITIVE_POINTS = 0,
		GL_TRIANGLES,					// PRIMITIVE_TRIANGLES,
		GL_TRIANGLE_FAN,				// PRIMITIVE_TRIANGLE_FAN,
		GL_TRIANGLE_STRIP,				// PRIMITIVE_TRIANGLE_STRIP,
		GL_LINES,						// PRIMITIVE_LINES
		GL_LINE_STRIP,					// PRIMITIVE_LINE_STRIP
		GL_LINE_LOOP,					// PRIMITIVE_LINE_LOOP
		GL_LINES_ADJACENCY,				// PRIMITIVE_LINES_ADJACENCY
		GL_LINE_STRIP_ADJACENCY,		// PRIMITIVE_LINE_STRIP_ADJACENCY
		GL_TRIANGLES_ADJACENCY,			// PRIMITIVE_TRIANGLES_ADJACENCY
		GL_TRIANGLE_STRIP_ADJACENCY,	// PRIMITIVE_TRIANGLE_STRIP_ADJACENCY
	};

	return de::getSizedArrayElement<DrawTestSpec::PRIMITIVE_LAST>(primitives, (int)primitive);
}

static deUint32 indexTypeToGL (DrawTestSpec::IndexType indexType)
{
	static const GLenum indexTypes[] =
	{
		GL_UNSIGNED_BYTE,	// INDEXTYPE_BYTE = 0,
		GL_UNSIGNED_SHORT,	// INDEXTYPE_SHORT,
		GL_UNSIGNED_INT,	// INDEXTYPE_INT,
	};

	return de::getSizedArrayElement<DrawTestSpec::INDEXTYPE_LAST>(indexTypes, (int)indexType);
}

static bool inputTypeIsFloatType (DrawTestSpec::InputType type)
{
	if (type == DrawTestSpec::INPUTTYPE_FLOAT)
		return true;
	if (type == DrawTestSpec::INPUTTYPE_FIXED)
		return true;
	if (type == DrawTestSpec::INPUTTYPE_HALF)
		return true;
	if (type == DrawTestSpec::INPUTTYPE_DOUBLE)
		return true;
	return false;
}

static bool outputTypeIsFloatType (DrawTestSpec::OutputType type)
{
	if (type == DrawTestSpec::OUTPUTTYPE_FLOAT
		|| type == DrawTestSpec::OUTPUTTYPE_VEC2
		|| type == DrawTestSpec::OUTPUTTYPE_VEC3
		|| type == DrawTestSpec::OUTPUTTYPE_VEC4)
		return true;

	return false;
}

static bool outputTypeIsIntType (DrawTestSpec::OutputType type)
{
	if (type == DrawTestSpec::OUTPUTTYPE_INT
		|| type == DrawTestSpec::OUTPUTTYPE_IVEC2
		|| type == DrawTestSpec::OUTPUTTYPE_IVEC3
		|| type == DrawTestSpec::OUTPUTTYPE_IVEC4)
		return true;

	return false;
}

static bool outputTypeIsUintType (DrawTestSpec::OutputType type)
{
	if (type == DrawTestSpec::OUTPUTTYPE_UINT
		|| type == DrawTestSpec::OUTPUTTYPE_UVEC2
		|| type == DrawTestSpec::OUTPUTTYPE_UVEC3
		|| type == DrawTestSpec::OUTPUTTYPE_UVEC4)
		return true;

	return false;
}

static size_t getElementCount (DrawTestSpec::Primitive primitive, size_t primitiveCount)
{
	switch (primitive)
	{
		case DrawTestSpec::PRIMITIVE_POINTS:						return primitiveCount;
		case DrawTestSpec::PRIMITIVE_TRIANGLES:						return primitiveCount * 3;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:					return primitiveCount + 2;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:				return primitiveCount + 2;
		case DrawTestSpec::PRIMITIVE_LINES:							return primitiveCount * 2;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP:					return primitiveCount + 1;
		case DrawTestSpec::PRIMITIVE_LINE_LOOP:						return (primitiveCount==1) ? (2) : (primitiveCount);
		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:				return primitiveCount * 4;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:			return primitiveCount + 3;
		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:			return primitiveCount * 6;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:		return primitiveCount * 2 + 4;
		default:
			DE_ASSERT(false);
			return 0;
	}
}

struct MethodInfo
{
	bool indexed;
	bool instanced;
	bool ranged;
	bool first;
	bool baseVertex;
	bool indirect;
};

static MethodInfo getMethodInfo (gls::DrawTestSpec::DrawMethod method)
{
	static const MethodInfo infos[] =
	{
		//	indexed		instanced	ranged		first		baseVertex	indirect
		{	false,		false,		false,		true,		false,		false	}, //!< DRAWMETHOD_DRAWARRAYS,
		{	false,		true,		false,		true,		false,		false	}, //!< DRAWMETHOD_DRAWARRAYS_INSTANCED,
		{	false,		true,		false,		true,		false,		true	}, //!< DRAWMETHOD_DRAWARRAYS_INDIRECT,
		{	true,		false,		false,		false,		false,		false	}, //!< DRAWMETHOD_DRAWELEMENTS,
		{	true,		false,		true,		false,		false,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_RANGED,
		{	true,		true,		false,		false,		false,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED,
		{	true,		true,		false,		false,		true,		true	}, //!< DRAWMETHOD_DRAWELEMENTS_INDIRECT,
		{	true,		false,		false,		false,		true,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_BASEVERTEX,
		{	true,		true,		false,		false,		true,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX,
		{	true,		false,		true,		false,		true,		false	}, //!< DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX,
	};

	return de::getSizedArrayElement<DrawTestSpec::DRAWMETHOD_LAST>(infos, (int)method);
}

template<class T>
inline static void alignmentSafeAssignment (char* dst, T val)
{
	std::memcpy(dst, &val, sizeof(T));
}

static bool checkSpecsShaderCompatible (const DrawTestSpec& a, const DrawTestSpec& b)
{
	// Only the attributes matter
	if (a.attribs.size() != b.attribs.size())
		return false;

	for (size_t ndx = 0; ndx < a.attribs.size(); ++ndx)
	{
		// Only the output type (== shader input type) matters and the usage in the shader.

		if (a.attribs[ndx].additionalPositionAttribute != b.attribs[ndx].additionalPositionAttribute)
			return false;

		// component counts need not to match
		if (outputTypeIsFloatType(a.attribs[ndx].outputType) && outputTypeIsFloatType(b.attribs[ndx].outputType))
			continue;
		if (outputTypeIsIntType(a.attribs[ndx].outputType) && outputTypeIsIntType(b.attribs[ndx].outputType))
			continue;
		if (outputTypeIsUintType(a.attribs[ndx].outputType) && outputTypeIsUintType(b.attribs[ndx].outputType))
			continue;

		return false;
	}

	return true;
}

// generate random vectors in a way that does not depend on argument evaluation order

tcu::Vec4 generateRandomVec4 (de::Random& random)
{
	tcu::Vec4 retVal;

	for (int i = 0; i < 4; ++i)
		retVal[i] = random.getFloat();

	return retVal;
}

tcu::IVec4 generateRandomIVec4 (de::Random& random)
{
	tcu::IVec4 retVal;

	for (int i = 0; i < 4; ++i)
		retVal[i] = random.getUint32();

	return retVal;
}

tcu::UVec4 generateRandomUVec4 (de::Random& random)
{
	tcu::UVec4 retVal;

	for (int i = 0; i < 4; ++i)
		retVal[i] = random.getUint32();

	return retVal;
}

// IterationLogSectionEmitter

class IterationLogSectionEmitter
{
public:
								IterationLogSectionEmitter		(tcu::TestLog& log, size_t testIteration, size_t testIterations, const std::string& description, bool enabled);
								~IterationLogSectionEmitter		(void);
private:
								IterationLogSectionEmitter		(const IterationLogSectionEmitter&); // delete
	IterationLogSectionEmitter&	operator=						(const IterationLogSectionEmitter&); // delete

	tcu::TestLog&				m_log;
	bool						m_enabled;
};

IterationLogSectionEmitter::IterationLogSectionEmitter (tcu::TestLog& log, size_t testIteration, size_t testIterations, const std::string& description, bool enabled)
	: m_log		(log)
	, m_enabled	(enabled)
{
	if (m_enabled)
	{
		std::ostringstream buf;
		buf << "Iteration " << (testIteration+1) << "/" << testIterations;

		if (!description.empty())
			buf << " - " << description;

		m_log << tcu::TestLog::Section(buf.str(), buf.str());
	}
}

IterationLogSectionEmitter::~IterationLogSectionEmitter (void)
{
	if (m_enabled)
		m_log << tcu::TestLog::EndSection;
}

// GLValue

class GLValue
{
public:

	template<class Type>
	class WrappedType
	{
	public:
		static WrappedType<Type>	create			(Type value)							{ WrappedType<Type> v; v.m_value = value; return v; }
		inline Type					getValue		(void) const							{ return m_value; }

		inline WrappedType<Type>	operator+		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create((Type)(m_value + other.getValue())); }
		inline WrappedType<Type>	operator*		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create((Type)(m_value * other.getValue())); }
		inline WrappedType<Type>	operator/		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create((Type)(m_value / other.getValue())); }
		inline WrappedType<Type>	operator-		(const WrappedType<Type>& other) const	{ return WrappedType<Type>::create((Type)(m_value - other.getValue())); }

		inline WrappedType<Type>&	operator+=		(const WrappedType<Type>& other)		{ m_value += other.getValue(); return *this; }
		inline WrappedType<Type>&	operator*=		(const WrappedType<Type>& other)		{ m_value *= other.getValue(); return *this; }
		inline WrappedType<Type>&	operator/=		(const WrappedType<Type>& other)		{ m_value /= other.getValue(); return *this; }
		inline WrappedType<Type>&	operator-=		(const WrappedType<Type>& other)		{ m_value -= other.getValue(); return *this; }

		inline bool					operator==		(const WrappedType<Type>& other) const	{ return m_value == other.m_value; }
		inline bool					operator!=		(const WrappedType<Type>& other) const	{ return m_value != other.m_value; }
		inline bool					operator<		(const WrappedType<Type>& other) const	{ return m_value < other.m_value; }
		inline bool					operator>		(const WrappedType<Type>& other) const	{ return m_value > other.m_value; }
		inline bool					operator<=		(const WrappedType<Type>& other) const	{ return m_value <= other.m_value; }
		inline bool					operator>=		(const WrappedType<Type>& other) const	{ return m_value >= other.m_value; }

		inline						operator Type	(void) const							{ return m_value; }
		template<class T>
		inline T					to				(void) const							{ return (T)m_value; }
	private:
		Type	m_value;
	};

	typedef WrappedType<deInt16>	Short;
	typedef WrappedType<deUint16>	Ushort;

	typedef WrappedType<deInt8>		Byte;
	typedef WrappedType<deUint8>	Ubyte;

	typedef WrappedType<float>		Float;
	typedef WrappedType<double>		Double;

	typedef WrappedType<deInt32>	Int;
	typedef WrappedType<deUint32>	Uint;

	class Half
	{
	public:
		static Half			create			(float value)				{ Half h; h.m_value = floatToHalf(value); return h; }
		inline deFloat16	getValue		(void) const				{ return m_value; }

		inline Half			operator+		(const Half& other) const	{ return create(halfToFloat(m_value) + halfToFloat(other.getValue())); }
		inline Half			operator*		(const Half& other) const	{ return create(halfToFloat(m_value) * halfToFloat(other.getValue())); }
		inline Half			operator/		(const Half& other) const	{ return create(halfToFloat(m_value) / halfToFloat(other.getValue())); }
		inline Half			operator-		(const Half& other) const	{ return create(halfToFloat(m_value) - halfToFloat(other.getValue())); }

		inline Half&		operator+=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) + halfToFloat(m_value)); return *this; }
		inline Half&		operator*=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) * halfToFloat(m_value)); return *this; }
		inline Half&		operator/=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) / halfToFloat(m_value)); return *this; }
		inline Half&		operator-=		(const Half& other)			{ m_value = floatToHalf(halfToFloat(other.getValue()) - halfToFloat(m_value)); return *this; }

		inline bool			operator==		(const Half& other) const	{ return m_value == other.m_value; }
		inline bool			operator!=		(const Half& other) const	{ return m_value != other.m_value; }
		inline bool			operator<		(const Half& other) const	{ return halfToFloat(m_value) < halfToFloat(other.m_value); }
		inline bool			operator>		(const Half& other) const	{ return halfToFloat(m_value) > halfToFloat(other.m_value); }
		inline bool			operator<=		(const Half& other) const	{ return halfToFloat(m_value) <= halfToFloat(other.m_value); }
		inline bool			operator>=		(const Half& other) const	{ return halfToFloat(m_value) >= halfToFloat(other.m_value); }

		template<class T>
		inline T			to				(void) const				{ return (T)halfToFloat(m_value); }

		inline static deFloat16	floatToHalf		(float f);
		inline static float		halfToFloat		(deFloat16 h);
	private:
		deFloat16 m_value;
	};

	class Fixed
	{
	public:
		static Fixed		create			(deInt32 value)				{ Fixed v; v.m_value = value; return v; }
		inline deInt32		getValue		(void) const				{ return m_value; }

		inline Fixed		operator+		(const Fixed& other) const	{ return create(m_value + other.getValue()); }
		inline Fixed		operator*		(const Fixed& other) const	{ return create(m_value * other.getValue()); }
		inline Fixed		operator/		(const Fixed& other) const	{ return create(m_value / other.getValue()); }
		inline Fixed		operator-		(const Fixed& other) const	{ return create(m_value - other.getValue()); }

		inline Fixed&		operator+=		(const Fixed& other)		{ m_value += other.getValue(); return *this; }
		inline Fixed&		operator*=		(const Fixed& other)		{ m_value *= other.getValue(); return *this; }
		inline Fixed&		operator/=		(const Fixed& other)		{ m_value /= other.getValue(); return *this; }
		inline Fixed&		operator-=		(const Fixed& other)		{ m_value -= other.getValue(); return *this; }

		inline bool			operator==		(const Fixed& other) const	{ return m_value == other.m_value; }
		inline bool			operator!=		(const Fixed& other) const	{ return m_value != other.m_value; }
		inline bool			operator<		(const Fixed& other) const	{ return m_value < other.m_value; }
		inline bool			operator>		(const Fixed& other) const	{ return m_value > other.m_value; }
		inline bool			operator<=		(const Fixed& other) const	{ return m_value <= other.m_value; }
		inline bool			operator>=		(const Fixed& other) const	{ return m_value >= other.m_value; }

		inline				operator deInt32 (void) const				{ return m_value; }
		template<class T>
		inline T			to				(void) const				{ return (T)m_value; }
	private:
		deInt32				m_value;
	};

	// \todo [mika] This is pretty messy
						GLValue			(void)			: type(DrawTestSpec::INPUTTYPE_LAST) {}
	explicit			GLValue			(Float value)	: type(DrawTestSpec::INPUTTYPE_FLOAT),				fl(value)	{}
	explicit			GLValue			(Fixed value)	: type(DrawTestSpec::INPUTTYPE_FIXED),				fi(value)	{}
	explicit			GLValue			(Byte value)	: type(DrawTestSpec::INPUTTYPE_BYTE),				b(value)	{}
	explicit			GLValue			(Ubyte value)	: type(DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE),		ub(value)	{}
	explicit			GLValue			(Short value)	: type(DrawTestSpec::INPUTTYPE_SHORT),				s(value)	{}
	explicit			GLValue			(Ushort value)	: type(DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT),		us(value)	{}
	explicit			GLValue			(Int value)		: type(DrawTestSpec::INPUTTYPE_INT),				i(value)	{}
	explicit			GLValue			(Uint value)	: type(DrawTestSpec::INPUTTYPE_UNSIGNED_INT),		ui(value)	{}
	explicit			GLValue			(Half value)	: type(DrawTestSpec::INPUTTYPE_HALF),				h(value)	{}
	explicit			GLValue			(Double value)	: type(DrawTestSpec::INPUTTYPE_DOUBLE),				d(value)	{}

	float				toFloat			(void) const;

	static GLValue		getMaxValue		(DrawTestSpec::InputType type);
	static GLValue		getMinValue		(DrawTestSpec::InputType type);

	DrawTestSpec::InputType	type;

	union
	{
		Float		fl;
		Fixed		fi;
		Double		d;
		Byte		b;
		Ubyte		ub;
		Short		s;
		Ushort		us;
		Int			i;
		Uint		ui;
		Half		h;
	};
};

inline deFloat16 GLValue::Half::floatToHalf (float f)
{
	// No denorm support.
	tcu::Float<deUint16, 5, 10, 15, tcu::FLOAT_HAS_SIGN> v(f);
	DE_ASSERT(!v.isNaN() && !v.isInf());
	return v.bits();
}

inline float GLValue::Half::halfToFloat (deFloat16 h)
{
	return tcu::Float16((deUint16)h).asFloat();
}

float GLValue::toFloat (void) const
{
	switch (type)
	{
		case DrawTestSpec::INPUTTYPE_FLOAT:
			return fl.getValue();
			break;

		case DrawTestSpec::INPUTTYPE_BYTE:
			return b.getValue();
			break;

		case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE:
			return ub.getValue();
			break;

		case DrawTestSpec::INPUTTYPE_SHORT:
			return s.getValue();
			break;

		case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT:
			return us.getValue();
			break;

		case DrawTestSpec::INPUTTYPE_FIXED:
		{
			int maxValue = 65536;
			return (float)(double(2 * fi.getValue() + 1) / (maxValue - 1));

			break;
		}

		case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:
			return (float)ui.getValue();
			break;

		case DrawTestSpec::INPUTTYPE_INT:
			return (float)i.getValue();
			break;

		case DrawTestSpec::INPUTTYPE_HALF:
			return h.to<float>();
			break;

		case DrawTestSpec::INPUTTYPE_DOUBLE:
			return d.to<float>();
			break;

		default:
			DE_ASSERT(false);
			return 0.0f;
			break;
	};
}

GLValue GLValue::getMaxValue (DrawTestSpec::InputType type)
{
	GLValue rangesHi[(int)DrawTestSpec::INPUTTYPE_LAST];

	rangesHi[(int)DrawTestSpec::INPUTTYPE_FLOAT]			= GLValue(Float::create(127.0f));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_DOUBLE]			= GLValue(Double::create(127.0f));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_BYTE]				= GLValue(Byte::create(127));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(255));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(65530));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_SHORT]			= GLValue(Short::create(32760));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_FIXED]			= GLValue(Fixed::create(32760));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_INT]				= GLValue(Int::create(2147483647));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_INT]		= GLValue(Uint::create(4294967295u));
	rangesHi[(int)DrawTestSpec::INPUTTYPE_HALF]				= GLValue(Half::create(256.0f));

	return rangesHi[(int)type];
}

GLValue GLValue::getMinValue (DrawTestSpec::InputType type)
{
	GLValue rangesLo[(int)DrawTestSpec::INPUTTYPE_LAST];

	rangesLo[(int)DrawTestSpec::INPUTTYPE_FLOAT]			= GLValue(Float::create(-127.0f));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_DOUBLE]			= GLValue(Double::create(-127.0f));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_BYTE]				= GLValue(Byte::create(-127));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE]	= GLValue(Ubyte::create(0));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT]	= GLValue(Ushort::create(0));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_SHORT]			= GLValue(Short::create(-32760));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_FIXED]			= GLValue(Fixed::create(-32760));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_INT]				= GLValue(Int::create(-2147483647));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_UNSIGNED_INT]		= GLValue(Uint::create(0));
	rangesLo[(int)DrawTestSpec::INPUTTYPE_HALF]				= GLValue(Half::create(-256.0f));

	return rangesLo[(int)type];
}

template<typename T>
struct GLValueTypeTraits;

template<> struct GLValueTypeTraits<GLValue::Float>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_FLOAT;			};
template<> struct GLValueTypeTraits<GLValue::Double> { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_DOUBLE;			};
template<> struct GLValueTypeTraits<GLValue::Byte>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_BYTE;			};
template<> struct GLValueTypeTraits<GLValue::Ubyte>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE;	};
template<> struct GLValueTypeTraits<GLValue::Ushort> { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT;	};
template<> struct GLValueTypeTraits<GLValue::Short>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_SHORT;			};
template<> struct GLValueTypeTraits<GLValue::Fixed>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_FIXED;			};
template<> struct GLValueTypeTraits<GLValue::Int>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_INT;			};
template<> struct GLValueTypeTraits<GLValue::Uint>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_UNSIGNED_INT;	};
template<> struct GLValueTypeTraits<GLValue::Half>	 { static const DrawTestSpec::InputType Type = DrawTestSpec::INPUTTYPE_HALF;			};

template<typename T>
inline T extractGLValue (const GLValue& v);

template<> GLValue::Float	inline extractGLValue<GLValue::Float>		(const GLValue& v) { return v.fl; };
template<> GLValue::Double	inline extractGLValue<GLValue::Double>		(const GLValue& v) { return v.d; };
template<> GLValue::Byte	inline extractGLValue<GLValue::Byte>		(const GLValue& v) { return v.b; };
template<> GLValue::Ubyte	inline extractGLValue<GLValue::Ubyte>		(const GLValue& v) { return v.ub; };
template<> GLValue::Ushort	inline extractGLValue<GLValue::Ushort>		(const GLValue& v) { return v.us; };
template<> GLValue::Short	inline extractGLValue<GLValue::Short>		(const GLValue& v) { return v.s; };
template<> GLValue::Fixed	inline extractGLValue<GLValue::Fixed>		(const GLValue& v) { return v.fi; };
template<> GLValue::Int		inline extractGLValue<GLValue::Int>			(const GLValue& v) { return v.i; };
template<> GLValue::Uint	inline extractGLValue<GLValue::Uint>		(const GLValue& v) { return v.ui; };
template<> GLValue::Half	inline extractGLValue<GLValue::Half>		(const GLValue& v) { return v.h; };

template<class T>
inline T getRandom (deRandom& rnd, T min, T max);

template<>
inline GLValue::Float getRandom (deRandom& rnd, GLValue::Float min, GLValue::Float max)
{
	if (max < min)
		return min;

	return GLValue::Float::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
}

template<>
inline GLValue::Double getRandom (deRandom& rnd, GLValue::Double min, GLValue::Double max)
{
	if (max < min)
		return min;

	return GLValue::Double::create(min + deRandom_getFloat(&rnd) * (max.to<float>() - min.to<float>()));
}

template<>
inline GLValue::Short getRandom (deRandom& rnd, GLValue::Short min, GLValue::Short max)
{
	if (max < min)
		return min;

	return GLValue::Short::create((min == max ? min : (deInt16)(min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>())))));
}

template<>
inline GLValue::Ushort getRandom (deRandom& rnd, GLValue::Ushort min, GLValue::Ushort max)
{
	if (max < min)
		return min;

	return GLValue::Ushort::create((min == max ? min : (deUint16)(min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>())))));
}

template<>
inline GLValue::Byte getRandom (deRandom& rnd, GLValue::Byte min, GLValue::Byte max)
{
	if (max < min)
		return min;

	return GLValue::Byte::create((min == max ? min : (deInt8)(min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>())))));
}

template<>
inline GLValue::Ubyte getRandom (deRandom& rnd, GLValue::Ubyte min, GLValue::Ubyte max)
{
	if (max < min)
		return min;

	return GLValue::Ubyte::create((min == max ? min : (deUint8)(min + (deRandom_getUint32(&rnd) % (max.to<int>() - min.to<int>())))));
}

template<>
inline GLValue::Fixed getRandom (deRandom& rnd, GLValue::Fixed min, GLValue::Fixed max)
{
	if (max < min)
		return min;

	return GLValue::Fixed::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
}

template<>
inline GLValue::Half getRandom (deRandom& rnd, GLValue::Half min, GLValue::Half max)
{
	if (max < min)
		return min;

	float fMax = max.to<float>();
	float fMin = min.to<float>();
	GLValue::Half h = GLValue::Half::create(fMin + deRandom_getFloat(&rnd) * (fMax - fMin));
	return h;
}

template<>
inline GLValue::Int getRandom (deRandom& rnd, GLValue::Int min, GLValue::Int max)
{
	if (max < min)
		return min;

	return GLValue::Int::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
}

template<>
inline GLValue::Uint getRandom (deRandom& rnd, GLValue::Uint min, GLValue::Uint max)
{
	if (max < min)
		return min;

	return GLValue::Uint::create((min == max ? min : min + (deRandom_getUint32(&rnd) % (max.to<deUint32>() - min.to<deUint32>()))));
}

// Minimum difference required between coordinates
template<class T>
inline T minValue (void);

template<>
inline GLValue::Float minValue (void)
{
	return GLValue::Float::create(4 * 1.0f);
}

template<>
inline GLValue::Double minValue (void)
{
	return GLValue::Double::create(4 * 1.0f);
}

template<>
inline GLValue::Short minValue (void)
{
	return GLValue::Short::create(4 * 256);
}

template<>
inline GLValue::Ushort minValue (void)
{
	return GLValue::Ushort::create(4 * 256);
}

template<>
inline GLValue::Byte minValue (void)
{
	return GLValue::Byte::create(4 * 1);
}

template<>
inline GLValue::Ubyte minValue (void)
{
	return GLValue::Ubyte::create(4 * 2);
}

template<>
inline GLValue::Fixed minValue (void)
{
	return GLValue::Fixed::create(4 * 1);
}

template<>
inline GLValue::Int minValue (void)
{
	return GLValue::Int::create(4 * 16777216);
}

template<>
inline GLValue::Uint minValue (void)
{
	return GLValue::Uint::create(4 * 16777216);
}

template<>
inline GLValue::Half minValue (void)
{
	return GLValue::Half::create(4 * 1.0f);
}

template<class T>
inline T abs (T val);

template<>
inline GLValue::Fixed abs (GLValue::Fixed val)
{
	return GLValue::Fixed::create(0x7FFFu & val.getValue());
}

template<>
inline GLValue::Ubyte abs (GLValue::Ubyte val)
{
	return val;
}

template<>
inline GLValue::Byte abs (GLValue::Byte val)
{
	return GLValue::Byte::create(0x7Fu & val.getValue());
}

template<>
inline GLValue::Ushort abs (GLValue::Ushort val)
{
	return val;
}

template<>
inline GLValue::Short abs (GLValue::Short val)
{
	return GLValue::Short::create(0x7FFFu & val.getValue());
}

template<>
inline GLValue::Float abs (GLValue::Float val)
{
	return GLValue::Float::create(std::fabs(val.to<float>()));
}

template<>
inline GLValue::Double abs (GLValue::Double val)
{
	return GLValue::Double::create(std::fabs(val.to<float>()));
}

template<>
inline GLValue::Uint abs (GLValue::Uint val)
{
	return val;
}

template<>
inline GLValue::Int abs (GLValue::Int val)
{
	return GLValue::Int::create(0x7FFFFFFFu & val.getValue());
}

template<>
inline GLValue::Half abs (GLValue::Half val)
{
	return GLValue::Half::create(std::fabs(val.to<float>()));
}

// AttributeArray

class AttributeArray
{
public:
								AttributeArray		(DrawTestSpec::Storage storage, sglr::Context& context);
								~AttributeArray		(void);

	void						data				(DrawTestSpec::Target target, size_t size, const char* data, DrawTestSpec::Usage usage);
	void						setupArray			(bool bound, int offset, int size, DrawTestSpec::InputType inType, DrawTestSpec::OutputType outType, bool normalized, int stride, int instanceDivisor, const rr::GenericVec4& defaultAttrib, bool isPositionAttr, bool bgraComponentOrder);
	void						bindAttribute		(deUint32 loc);
	void						bindIndexArray		(DrawTestSpec::Target storage);

	int							getComponentCount	(void) const { return m_componentCount; }
	DrawTestSpec::Target		getTarget			(void) const { return m_target; }
	DrawTestSpec::InputType		getInputType		(void) const { return m_inputType; }
	DrawTestSpec::OutputType	getOutputType		(void) const { return m_outputType; }
	DrawTestSpec::Storage		getStorageType		(void) const { return m_storage; }
	bool						getNormalized		(void) const { return m_normalize; }
	int							getStride			(void) const { return m_stride; }
	bool						isBound				(void) const { return m_bound; }
	bool						isPositionAttribute	(void) const { return m_isPositionAttr; }

private:
	DrawTestSpec::Storage		m_storage;
	sglr::Context&				m_ctx;
	deUint32					m_glBuffer;

	int							m_size;
	char*						m_data;
	int							m_componentCount;
	bool						m_bound;
	DrawTestSpec::Target		m_target;
	DrawTestSpec::InputType		m_inputType;
	DrawTestSpec::OutputType	m_outputType;
	bool						m_normalize;
	int							m_stride;
	int							m_offset;
	rr::GenericVec4				m_defaultAttrib;
	int							m_instanceDivisor;
	bool						m_isPositionAttr;
	bool						m_bgraOrder;
};

AttributeArray::AttributeArray (DrawTestSpec::Storage storage, sglr::Context& context)
	: m_storage			(storage)
	, m_ctx				(context)
	, m_glBuffer		(0)
	, m_size			(0)
	, m_data			(DE_NULL)
	, m_componentCount	(1)
	, m_bound			(false)
	, m_target			(DrawTestSpec::TARGET_ARRAY)
	, m_inputType		(DrawTestSpec::INPUTTYPE_FLOAT)
	, m_outputType		(DrawTestSpec::OUTPUTTYPE_VEC4)
	, m_normalize		(false)
	, m_stride			(0)
	, m_offset			(0)
	, m_instanceDivisor	(0)
	, m_isPositionAttr	(false)
	, m_bgraOrder		(false)
{
	if (m_storage == DrawTestSpec::STORAGE_BUFFER)
	{
		m_ctx.genBuffers(1, &m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glGenBuffers()");
	}
}

AttributeArray::~AttributeArray	(void)
{
	if (m_storage == DrawTestSpec::STORAGE_BUFFER)
	{
		m_ctx.deleteBuffers(1, &m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDeleteBuffers()");
	}
	else if (m_storage == DrawTestSpec::STORAGE_USER)
		delete[] m_data;
	else
		DE_ASSERT(false);
}

void AttributeArray::data (DrawTestSpec::Target target, size_t size, const char* ptr, DrawTestSpec::Usage usage)
{
	m_size = (int)size;
	m_target = target;

	if (m_storage == DrawTestSpec::STORAGE_BUFFER)
	{
		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");

		m_ctx.bufferData(targetToGL(target), size, ptr, usageToGL(usage));
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBufferData()");
	}
	else if (m_storage == DrawTestSpec::STORAGE_USER)
	{
		if (m_data)
			delete[] m_data;

		m_data = new char[size];
		std::memcpy(m_data, ptr, size);
	}
	else
		DE_ASSERT(false);
}

void AttributeArray::setupArray (bool bound, int offset, int size, DrawTestSpec::InputType inputType, DrawTestSpec::OutputType outType, bool normalized, int stride, int instanceDivisor, const rr::GenericVec4& defaultAttrib, bool isPositionAttr, bool bgraComponentOrder)
{
	m_componentCount	= size;
	m_bound				= bound;
	m_inputType			= inputType;
	m_outputType		= outType;
	m_normalize			= normalized;
	m_stride			= stride;
	m_offset			= offset;
	m_defaultAttrib		= defaultAttrib;
	m_instanceDivisor	= instanceDivisor;
	m_isPositionAttr	= isPositionAttr;
	m_bgraOrder			= bgraComponentOrder;
}

void AttributeArray::bindAttribute (deUint32 loc)
{
	if (!isBound())
	{
		switch (m_inputType)
		{
			case DrawTestSpec::INPUTTYPE_FLOAT:
			{
				tcu::Vec4 attr = m_defaultAttrib.get<float>();

				switch (m_componentCount)
				{
					case 1: m_ctx.vertexAttrib1f(loc, attr.x()); break;
					case 2: m_ctx.vertexAttrib2f(loc, attr.x(), attr.y()); break;
					case 3: m_ctx.vertexAttrib3f(loc, attr.x(), attr.y(), attr.z()); break;
					case 4: m_ctx.vertexAttrib4f(loc, attr.x(), attr.y(), attr.z(), attr.w()); break;
					default: DE_ASSERT(DE_FALSE); break;
				}
				break;
			}
			case DrawTestSpec::INPUTTYPE_INT:
			{
				tcu::IVec4 attr = m_defaultAttrib.get<deInt32>();
				m_ctx.vertexAttribI4i(loc, attr.x(), attr.y(), attr.z(), attr.w());
				break;
			}
			case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:
			{
				tcu::UVec4 attr = m_defaultAttrib.get<deUint32>();
				m_ctx.vertexAttribI4ui(loc, attr.x(), attr.y(), attr.z(), attr.w());
				break;
			}
			default:
				DE_ASSERT(DE_FALSE);
				break;
		}
	}
	else
	{
		const deUint8* basePtr = DE_NULL;

		if (m_storage == DrawTestSpec::STORAGE_BUFFER)
		{
			m_ctx.bindBuffer(targetToGL(m_target), m_glBuffer);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");

			basePtr = DE_NULL;
		}
		else if (m_storage == DrawTestSpec::STORAGE_USER)
		{
			m_ctx.bindBuffer(targetToGL(m_target), 0);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glBindBuffer()");

			basePtr = (const deUint8*)m_data;
		}
		else
			DE_ASSERT(DE_FALSE);

		if (!inputTypeIsFloatType(m_inputType))
		{
			// Input is not float type

			if (outputTypeIsFloatType(m_outputType))
			{
				const int size = (m_bgraOrder) ? (GL_BGRA) : (m_componentCount);

				DE_ASSERT(!(m_bgraOrder && m_componentCount != 4));

				// Output type is float type
				m_ctx.vertexAttribPointer(loc, size, inputTypeToGL(m_inputType), m_normalize, m_stride, basePtr + m_offset);
				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
			}
			else
			{
				// Output type is int type
				m_ctx.vertexAttribIPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_stride, basePtr + m_offset);
				GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribIPointer()");
			}
		}
		else
		{
			// Input type is float type

			// Output type must be float type
			DE_ASSERT(outputTypeIsFloatType(m_outputType));

			m_ctx.vertexAttribPointer(loc, m_componentCount, inputTypeToGL(m_inputType), m_normalize, m_stride, basePtr + m_offset);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glVertexAttribPointer()");
		}

		if (m_instanceDivisor)
			m_ctx.vertexAttribDivisor(loc, m_instanceDivisor);
	}
}

void AttributeArray::bindIndexArray (DrawTestSpec::Target target)
{
	if (m_storage == DrawTestSpec::STORAGE_USER)
	{
	}
	else if (m_storage == DrawTestSpec::STORAGE_BUFFER)
	{
		m_ctx.bindBuffer(targetToGL(target), m_glBuffer);
	}
}

// DrawTestShaderProgram

class DrawTestShaderProgram : public sglr::ShaderProgram
{
public:
												DrawTestShaderProgram		(const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays);

	void										shadeVertices				(const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const;
	void										shadeFragments				(rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const;

private:
	static std::string							genVertexSource				(const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays);
	static std::string							genFragmentSource			(const glu::RenderContext& ctx);
	static void									generateShaderParams		(std::map<std::string, std::string>& params, glu::ContextType type);
	static rr::GenericVecType					mapOutputType				(const DrawTestSpec::OutputType& type);
	static int									getComponentCount			(const DrawTestSpec::OutputType& type);

	static sglr::pdec::ShaderProgramDeclaration createProgramDeclaration	(const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays);

	std::vector<int>							m_componentCount;
	std::vector<bool>							m_isCoord;
	std::vector<rr::GenericVecType>				m_attrType;
};

DrawTestShaderProgram::DrawTestShaderProgram (const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays)
	: sglr::ShaderProgram	(createProgramDeclaration(ctx, arrays))
	, m_componentCount		(arrays.size())
	, m_isCoord				(arrays.size())
	, m_attrType			(arrays.size())
{
	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
	{
		m_componentCount[arrayNdx]	= getComponentCount(arrays[arrayNdx]->getOutputType());
		m_isCoord[arrayNdx]			= arrays[arrayNdx]->isPositionAttribute();
		m_attrType[arrayNdx]		= mapOutputType(arrays[arrayNdx]->getOutputType());
	}
}

template <typename T>
void calcShaderColorCoord (tcu::Vec2& coord, tcu::Vec3& color, const tcu::Vector<T, 4>& attribValue, bool isCoordinate, int numComponents)
{
	if (isCoordinate)
		switch (numComponents)
		{
			case 1:	coord += tcu::Vec2((float)attribValue.x(),							(float)attribValue.x());							break;
			case 2:	coord += tcu::Vec2((float)attribValue.x(),							(float)attribValue.y());							break;
			case 3:	coord += tcu::Vec2((float)attribValue.x() + (float)attribValue.z(),	(float)attribValue.y());							break;
			case 4:	coord += tcu::Vec2((float)attribValue.x() + (float)attribValue.z(),	(float)attribValue.y() + (float)attribValue.w());	break;

			default:
				DE_ASSERT(false);
		}
	else
	{
		switch (numComponents)
		{
			case 1:
				color = color * (float)attribValue.x();
				break;

			case 2:
				color.x() = color.x() * (float)attribValue.x();
				color.y() = color.y() * (float)attribValue.y();
				break;

			case 3:
				color.x() = color.x() * (float)attribValue.x();
				color.y() = color.y() * (float)attribValue.y();
				color.z() = color.z() * (float)attribValue.z();
				break;

			case 4:
				color.x() = color.x() * (float)attribValue.x() * (float)attribValue.w();
				color.y() = color.y() * (float)attribValue.y() * (float)attribValue.w();
				color.z() = color.z() * (float)attribValue.z() * (float)attribValue.w();
				break;

			default:
				DE_ASSERT(false);
		}
	}
}

void DrawTestShaderProgram::shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
{
	const float	u_coordScale = getUniformByName("u_coordScale").value.f;
	const float u_colorScale = getUniformByName("u_colorScale").value.f;

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		const size_t varyingLocColor = 0;

		rr::VertexPacket& packet = *packets[packetNdx];

		// Calc output color
		tcu::Vec2 coord = tcu::Vec2(0.0, 0.0);
		tcu::Vec3 color = tcu::Vec3(1.0, 1.0, 1.0);

		for (int attribNdx = 0; attribNdx < (int)m_attrType.size(); attribNdx++)
		{
			const int	numComponents	= m_componentCount[attribNdx];
			const bool	isCoord			= m_isCoord[attribNdx];

			switch (m_attrType[attribNdx])
			{
				case rr::GENERICVECTYPE_FLOAT:	calcShaderColorCoord(coord, color, rr::readVertexAttribFloat(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents);	break;
				case rr::GENERICVECTYPE_INT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribInt	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents);	break;
				case rr::GENERICVECTYPE_UINT32:	calcShaderColorCoord(coord, color, rr::readVertexAttribUint	(inputs[attribNdx], packet.instanceNdx, packet.vertexNdx), isCoord, numComponents);	break;
				default:
					DE_ASSERT(false);
			}
		}

		// Transform position
		{
			packet.position = tcu::Vec4(u_coordScale * coord.x(), u_coordScale * coord.y(), 1.0f, 1.0f);
			packet.pointSize = 1.0f;
		}

		// Pass color to FS
		{
			packet.outputs[varyingLocColor] = tcu::Vec4(u_colorScale * color.x(), u_colorScale * color.y(), u_colorScale * color.z(), 1.0f) * 0.5f + tcu::Vec4(0.5f, 0.5f, 0.5f, 0.5f);
		}
	}
}

void DrawTestShaderProgram::shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
{
	const size_t varyingLocColor = 0;

	for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
	{
		rr::FragmentPacket& packet = packets[packetNdx];

		for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
			rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, varyingLocColor, fragNdx));
	}
}

std::string DrawTestShaderProgram::genVertexSource (const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays)
{
	std::map<std::string, std::string>	params;
	std::stringstream					vertexShaderTmpl;

	generateShaderParams(params, ctx.getType());

	vertexShaderTmpl << "${VTX_HDR}";

	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
	{
		vertexShaderTmpl
			<< "${VTX_IN} highp " << outputTypeToGLType(arrays[arrayNdx]->getOutputType()) << " a_" << arrayNdx << ";\n";
	}

	vertexShaderTmpl <<
		"uniform highp float u_coordScale;\n"
		"uniform highp float u_colorScale;\n"
		"${VTX_OUT} ${COL_PRECISION} vec4 v_color;\n"
		"void main(void)\n"
		"{\n"
		"\tgl_PointSize = 1.0;\n"
		"\thighp vec2 coord = vec2(0.0, 0.0);\n"
		"\thighp vec3 color = vec3(1.0, 1.0, 1.0);\n";

	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
	{
		const bool isPositionAttr = arrays[arrayNdx]->isPositionAttribute();

		if (isPositionAttr)
		{
			switch (arrays[arrayNdx]->getOutputType())
			{
				case (DrawTestSpec::OUTPUTTYPE_FLOAT):
				case (DrawTestSpec::OUTPUTTYPE_INT):
				case (DrawTestSpec::OUTPUTTYPE_UINT):
					vertexShaderTmpl <<
						"\tcoord += vec2(float(a_" << arrayNdx << "), float(a_" << arrayNdx << "));\n";
					break;

				case (DrawTestSpec::OUTPUTTYPE_VEC2):
				case (DrawTestSpec::OUTPUTTYPE_IVEC2):
				case (DrawTestSpec::OUTPUTTYPE_UVEC2):
					vertexShaderTmpl <<
						"\tcoord += vec2(a_" << arrayNdx << ".xy);\n";
					break;

				case (DrawTestSpec::OUTPUTTYPE_VEC3):
				case (DrawTestSpec::OUTPUTTYPE_IVEC3):
				case (DrawTestSpec::OUTPUTTYPE_UVEC3):
					vertexShaderTmpl <<
						"\tcoord += vec2(a_" << arrayNdx << ".xy);\n"
						"\tcoord.x += float(a_" << arrayNdx << ".z);\n";
					break;

				case (DrawTestSpec::OUTPUTTYPE_VEC4):
				case (DrawTestSpec::OUTPUTTYPE_IVEC4):
				case (DrawTestSpec::OUTPUTTYPE_UVEC4):
					vertexShaderTmpl <<
						"\tcoord += vec2(a_" << arrayNdx << ".xy);\n"
						"\tcoord += vec2(a_" << arrayNdx << ".zw);\n";
					break;

				default:
					DE_ASSERT(false);
					break;
			}
		}
		else
		{
			switch (arrays[arrayNdx]->getOutputType())
			{
				case (DrawTestSpec::OUTPUTTYPE_FLOAT):
				case (DrawTestSpec::OUTPUTTYPE_INT):
				case (DrawTestSpec::OUTPUTTYPE_UINT):
					vertexShaderTmpl <<
						"\tcolor = color * float(a_" << arrayNdx << ");\n";
					break;

				case (DrawTestSpec::OUTPUTTYPE_VEC2):
				case (DrawTestSpec::OUTPUTTYPE_IVEC2):
				case (DrawTestSpec::OUTPUTTYPE_UVEC2):
					vertexShaderTmpl <<
						"\tcolor.rg = color.rg * vec2(a_" << arrayNdx << ".xy);\n";
					break;

				case (DrawTestSpec::OUTPUTTYPE_VEC3):
				case (DrawTestSpec::OUTPUTTYPE_IVEC3):
				case (DrawTestSpec::OUTPUTTYPE_UVEC3):
					vertexShaderTmpl <<
						"\tcolor = color.rgb * vec3(a_" << arrayNdx << ".xyz);\n";
					break;

				case (DrawTestSpec::OUTPUTTYPE_VEC4):
				case (DrawTestSpec::OUTPUTTYPE_IVEC4):
				case (DrawTestSpec::OUTPUTTYPE_UVEC4):
					vertexShaderTmpl <<
						"\tcolor = color.rgb * vec3(a_" << arrayNdx << ".xyz) * float(a_" << arrayNdx << ".w);\n";
					break;

				default:
					DE_ASSERT(false);
					break;
			}
		}
	}

	vertexShaderTmpl <<
		"\tv_color = vec4(u_colorScale * color, 1.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5);\n"
		"\tgl_Position = vec4(u_coordScale * coord, 1.0, 1.0);\n"
		"}\n";

	return tcu::StringTemplate(vertexShaderTmpl.str().c_str()).specialize(params);
}

std::string DrawTestShaderProgram::genFragmentSource (const glu::RenderContext& ctx)
{
	std::map<std::string, std::string> params;

	generateShaderParams(params, ctx.getType());

	static const char* fragmentShaderTmpl =
		"${FRAG_HDR}"
		"${FRAG_IN} ${COL_PRECISION} vec4 v_color;\n"
		"void main(void)\n"
		"{\n"
		"\t${FRAG_COLOR} = v_color;\n"
		"}\n";

	return tcu::StringTemplate(fragmentShaderTmpl).specialize(params);
}

void DrawTestShaderProgram::generateShaderParams (std::map<std::string, std::string>& params, glu::ContextType type)
{
	if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_300_ES))
	{
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
		params["VTX_HDR"]		= "#version 300 es\n";
		params["FRAG_HDR"]		= "#version 300 es\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
		params["COL_PRECISION"]	= "mediump";
	}
	else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_100_ES))
	{
		params["VTX_IN"]		= "attribute";
		params["VTX_OUT"]		= "varying";
		params["FRAG_IN"]		= "varying";
		params["FRAG_COLOR"]	= "gl_FragColor";
		params["VTX_HDR"]		= "";
		params["FRAG_HDR"]		= "";
		params["COL_PRECISION"]	= "mediump";
	}
	else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_430))
	{
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
		params["VTX_HDR"]		= "#version 430\n";
		params["FRAG_HDR"]		= "#version 430\nlayout(location = 0) out highp vec4 dEQP_FragColor;\n";
		params["COL_PRECISION"]	= "highp";
	}
	else if (glu::isGLSLVersionSupported(type, glu::GLSL_VERSION_330))
	{
		params["VTX_IN"]		= "in";
		params["VTX_OUT"]		= "out";
		params["FRAG_IN"]		= "in";
		params["FRAG_COLOR"]	= "dEQP_FragColor";
		params["VTX_HDR"]		= "#version 330\n";
		params["FRAG_HDR"]		= "#version 330\nlayout(location = 0) out mediump vec4 dEQP_FragColor;\n";
		params["COL_PRECISION"]	= "mediump";
	}
	else
		DE_ASSERT(DE_FALSE);
}

rr::GenericVecType DrawTestShaderProgram::mapOutputType (const DrawTestSpec::OutputType& type)
{
	switch (type)
	{
		case (DrawTestSpec::OUTPUTTYPE_FLOAT):
		case (DrawTestSpec::OUTPUTTYPE_VEC2):
		case (DrawTestSpec::OUTPUTTYPE_VEC3):
		case (DrawTestSpec::OUTPUTTYPE_VEC4):
			return rr::GENERICVECTYPE_FLOAT;

		case (DrawTestSpec::OUTPUTTYPE_INT):
		case (DrawTestSpec::OUTPUTTYPE_IVEC2):
		case (DrawTestSpec::OUTPUTTYPE_IVEC3):
		case (DrawTestSpec::OUTPUTTYPE_IVEC4):
			return rr::GENERICVECTYPE_INT32;

		case (DrawTestSpec::OUTPUTTYPE_UINT):
		case (DrawTestSpec::OUTPUTTYPE_UVEC2):
		case (DrawTestSpec::OUTPUTTYPE_UVEC3):
		case (DrawTestSpec::OUTPUTTYPE_UVEC4):
			return rr::GENERICVECTYPE_UINT32;

		default:
			DE_ASSERT(false);
			return rr::GENERICVECTYPE_LAST;
	}
}

int DrawTestShaderProgram::getComponentCount (const DrawTestSpec::OutputType& type)
{
	switch (type)
	{
		case (DrawTestSpec::OUTPUTTYPE_FLOAT):
		case (DrawTestSpec::OUTPUTTYPE_INT):
		case (DrawTestSpec::OUTPUTTYPE_UINT):
			return 1;

		case (DrawTestSpec::OUTPUTTYPE_VEC2):
		case (DrawTestSpec::OUTPUTTYPE_IVEC2):
		case (DrawTestSpec::OUTPUTTYPE_UVEC2):
			return 2;

		case (DrawTestSpec::OUTPUTTYPE_VEC3):
		case (DrawTestSpec::OUTPUTTYPE_IVEC3):
		case (DrawTestSpec::OUTPUTTYPE_UVEC3):
			return 3;

		case (DrawTestSpec::OUTPUTTYPE_VEC4):
		case (DrawTestSpec::OUTPUTTYPE_IVEC4):
		case (DrawTestSpec::OUTPUTTYPE_UVEC4):
			return 4;

		default:
			DE_ASSERT(false);
			return 0;
	}
}

sglr::pdec::ShaderProgramDeclaration DrawTestShaderProgram::createProgramDeclaration (const glu::RenderContext& ctx, const std::vector<AttributeArray*>& arrays)
{
	sglr::pdec::ShaderProgramDeclaration decl;

	for (int arrayNdx = 0; arrayNdx < (int)arrays.size(); arrayNdx++)
		decl << sglr::pdec::VertexAttribute(std::string("a_") + de::toString(arrayNdx), mapOutputType(arrays[arrayNdx]->getOutputType()));

	decl << sglr::pdec::VertexToFragmentVarying(rr::GENERICVECTYPE_FLOAT);
	decl << sglr::pdec::FragmentOutput(rr::GENERICVECTYPE_FLOAT);

	decl << sglr::pdec::VertexSource(genVertexSource(ctx, arrays));
	decl << sglr::pdec::FragmentSource(genFragmentSource(ctx));

	decl << sglr::pdec::Uniform("u_coordScale", glu::TYPE_FLOAT);
	decl << sglr::pdec::Uniform("u_colorScale", glu::TYPE_FLOAT);

	return decl;
}

class RandomArrayGenerator
{
public:
	static char*			generateArray			(int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type);
	static char*			generateIndices			(int seed, int elementCount, DrawTestSpec::IndexType type, int offset, int min, int max, int indexBase);
	static rr::GenericVec4	generateAttributeValue	(int seed, DrawTestSpec::InputType type);

private:
	template<typename T>
	static char*			createIndices			(int seed, int elementCount, int offset, int min, int max, int indexBase);

	static char*			generateBasicArray		(int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type);
	template<typename T, typename GLType>
	static char*			createBasicArray		(int seed, int elementCount, int componentCount, int offset, int stride);
	static char*			generatePackedArray		(int seed, int elementCount, int componentCount, int offset, int stride);
};

char* RandomArrayGenerator::generateArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type)
{
	if (type == DrawTestSpec::INPUTTYPE_INT_2_10_10_10 || type == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
		return generatePackedArray(seed, elementCount, componentCount, offset, stride);
	else
		return generateBasicArray(seed, elementCount, componentCount, offset, stride, type);
}

char* RandomArrayGenerator::generateBasicArray (int seed, int elementCount, int componentCount, int offset, int stride, DrawTestSpec::InputType type)
{
	switch (type)
	{
		case DrawTestSpec::INPUTTYPE_FLOAT:				return createBasicArray<float,		GLValue::Float>	(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_DOUBLE:			return createBasicArray<double,		GLValue::Double>(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_SHORT:				return createBasicArray<deInt16,	GLValue::Short>	(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT:	return createBasicArray<deUint16,	GLValue::Ushort>(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_BYTE:				return createBasicArray<deInt8,		GLValue::Byte>	(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE:		return createBasicArray<deUint8,	GLValue::Ubyte>	(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_FIXED:				return createBasicArray<deInt32,	GLValue::Fixed>	(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_INT:				return createBasicArray<deInt32,	GLValue::Int>	(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:		return createBasicArray<deUint32,	GLValue::Uint>	(seed, elementCount, componentCount, offset, stride);
		case DrawTestSpec::INPUTTYPE_HALF:				return createBasicArray<deFloat16,	GLValue::Half>	(seed, elementCount, componentCount, offset, stride);
		default:
			DE_ASSERT(false);
			break;
	}
	return DE_NULL;
}

#if (DE_COMPILER == DE_COMPILER_GCC) && (__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)
	// GCC 4.8/4.9 incorrectly emits array-bounds warning from createBasicArray()
#	define GCC_ARRAY_BOUNDS_FALSE_NEGATIVE 1
#endif

#if defined(GCC_ARRAY_BOUNDS_FALSE_NEGATIVE)
#	pragma GCC diagnostic push
#	pragma GCC diagnostic ignored "-Warray-bounds"
#endif

template<typename T, typename GLType>
char* RandomArrayGenerator::createBasicArray (int seed, int elementCount, int componentCount, int offset, int stride)
{
	DE_ASSERT(componentCount >= 1 && componentCount <= 4);

	const GLType min = extractGLValue<GLType>(GLValue::getMinValue(GLValueTypeTraits<GLType>::Type));
	const GLType max = extractGLValue<GLType>(GLValue::getMaxValue(GLValueTypeTraits<GLType>::Type));

	const size_t componentSize	= sizeof(T);
	const size_t elementSize	= componentSize * componentCount;
	const size_t bufferSize		= offset + (elementCount - 1) * stride + elementSize;

	char* data = new char[bufferSize];
	char* writePtr = data + offset;

	GLType previousComponents[4];

	deRandom rnd;
	deRandom_init(&rnd, seed);

	for (int vertexNdx = 0; vertexNdx < elementCount; vertexNdx++)
	{
		GLType components[4];

		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
		{
			components[componentNdx] = getRandom<GLType>(rnd, min, max);

			// Try to not create vertex near previous
			if (vertexNdx != 0 && abs(components[componentNdx] - previousComponents[componentNdx]) < minValue<GLType>())
			{
				// Too close, try again (but only once)
				components[componentNdx] = getRandom<GLType>(rnd, min, max);
			}
		}

		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
			previousComponents[componentNdx] = components[componentNdx];

		for (int componentNdx = 0; componentNdx < componentCount; componentNdx++)
			alignmentSafeAssignment(writePtr + componentNdx*componentSize, components[componentNdx].getValue());

		writePtr += stride;
	}

	return data;
}

#if defined(GCC_ARRAY_BOUNDS_FALSE_NEGATIVE)
#	pragma GCC diagnostic pop
#endif

char* RandomArrayGenerator::generatePackedArray (int seed, int elementCount, int componentCount, int offset, int stride)
{
	DE_ASSERT(componentCount == 4);
	DE_UNREF(componentCount);

	const deUint32 limit10		= (1 << 10);
	const deUint32 limit2		= (1 << 2);
	const size_t elementSize	= 4;
	const size_t bufferSize		= offset + (elementCount - 1) * stride + elementSize;

	char* data = new char[bufferSize];
	char* writePtr = data + offset;

	deRandom rnd;
	deRandom_init(&rnd, seed);

	for (int vertexNdx = 0; vertexNdx < elementCount; vertexNdx++)
	{
		const deUint32 x			= deRandom_getUint32(&rnd) % limit10;
		const deUint32 y			= deRandom_getUint32(&rnd) % limit10;
		const deUint32 z			= deRandom_getUint32(&rnd) % limit10;
		const deUint32 w			= deRandom_getUint32(&rnd) % limit2;
		const deUint32 packedValue	= (w << 30) | (z << 20) | (y << 10) | (x);

		alignmentSafeAssignment(writePtr, packedValue);
		writePtr += stride;
	}

	return data;
}

char* RandomArrayGenerator::generateIndices (int seed, int elementCount, DrawTestSpec::IndexType type, int offset, int min, int max, int indexBase)
{
	char* data = DE_NULL;

	switch (type)
	{
		case DrawTestSpec::INDEXTYPE_BYTE:
			data = createIndices<deUint8>(seed, elementCount, offset, min, max, indexBase);
			break;

		case DrawTestSpec::INDEXTYPE_SHORT:
			data = createIndices<deUint16>(seed, elementCount, offset, min, max, indexBase);
			break;

		case DrawTestSpec::INDEXTYPE_INT:
			data = createIndices<deUint32>(seed, elementCount, offset, min, max, indexBase);
			break;

		default:
			DE_ASSERT(false);
			break;
	}

	return data;
}

template<typename T>
char* RandomArrayGenerator::createIndices (int seed, int elementCount, int offset, int min, int max, int indexBase)
{
	const size_t elementSize	= sizeof(T);
	const size_t bufferSize		= offset + elementCount * elementSize;

	char* data = new char[bufferSize];
	char* writePtr = data + offset;

	deUint32 oldNdx1 = deUint32(-1);
	deUint32 oldNdx2 = deUint32(-1);

	deRandom rnd;
	deRandom_init(&rnd, seed);

	DE_ASSERT(indexBase >= 0); // watch for underflows

	if (min < 0 || (size_t)min > std::numeric_limits<T>::max() ||
		max < 0 || (size_t)max > std::numeric_limits<T>::max() ||
		min > max)
		DE_FATAL("Invalid range");

	for (int elementNdx = 0; elementNdx < elementCount; ++elementNdx)
	{
		deUint32 ndx = getRandom(rnd, GLValue::Uint::create(min), GLValue::Uint::create(max)).getValue();

		// Try not to generate same index as any of previous two. This prevents
		// generation of degenerate triangles and lines. If [min, max] is too
		// small this cannot be guaranteed.

		if (ndx == oldNdx1)			++ndx;
		if (ndx > (deUint32)max)	ndx = min;
		if (ndx == oldNdx2)			++ndx;
		if (ndx > (deUint32)max)	ndx = min;
		if (ndx == oldNdx1)			++ndx;
		if (ndx > (deUint32)max)	ndx = min;

		oldNdx2 = oldNdx1;
		oldNdx1 = ndx;

		ndx += indexBase;

		alignmentSafeAssignment<T>(writePtr + elementSize * elementNdx, T(ndx));
	}

	return data;
}

rr::GenericVec4	RandomArrayGenerator::generateAttributeValue (int seed, DrawTestSpec::InputType type)
{
	de::Random random(seed);

	switch (type)
	{
		case DrawTestSpec::INPUTTYPE_FLOAT:
			return rr::GenericVec4(generateRandomVec4(random));

		case DrawTestSpec::INPUTTYPE_INT:
			return rr::GenericVec4(generateRandomIVec4(random));

		case DrawTestSpec::INPUTTYPE_UNSIGNED_INT:
			return rr::GenericVec4(generateRandomUVec4(random));

		default:
			DE_ASSERT(false);
			return rr::GenericVec4(tcu::Vec4(1, 1, 1, 1));
	}
}

} // anonymous

// AttributePack

class AttributePack
{
public:

								AttributePack		(tcu::TestContext& testCtx, glu::RenderContext& renderCtx, sglr::Context& drawContext, const tcu::UVec2& screenSize, bool useVao, bool logEnabled);
								~AttributePack		(void);

	AttributeArray*				getArray			(int i);
	int							getArrayCount		(void);

	void						newArray			(DrawTestSpec::Storage storage);
	void						clearArrays			(void);
	void						updateProgram		(void);

	void						render				(DrawTestSpec::Primitive primitive, DrawTestSpec::DrawMethod drawMethod, int firstVertex, int vertexCount, DrawTestSpec::IndexType indexType, const void* indexOffset, int rangeStart, int rangeEnd, int instanceCount, int indirectOffset, int baseVertex, float coordScale, float colorScale, AttributeArray* indexArray);

	const tcu::Surface&			getSurface			(void) const { return m_screen; }
private:
	tcu::TestContext&			m_testCtx;
	glu::RenderContext&			m_renderCtx;
	sglr::Context&				m_ctx;

	std::vector<AttributeArray*>m_arrays;
	sglr::ShaderProgram*		m_program;
	tcu::Surface				m_screen;
	const bool					m_useVao;
	const bool					m_logEnabled;
	deUint32					m_programID;
	deUint32					m_vaoID;
};

AttributePack::AttributePack (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, sglr::Context& drawContext, const tcu::UVec2& screenSize, bool useVao, bool logEnabled)
	: m_testCtx		(testCtx)
	, m_renderCtx	(renderCtx)
	, m_ctx			(drawContext)
	, m_program		(DE_NULL)
	, m_screen		(screenSize.x(), screenSize.y())
	, m_useVao		(useVao)
	, m_logEnabled	(logEnabled)
	, m_programID	(0)
	, m_vaoID		(0)
{
	if (m_useVao)
		m_ctx.genVertexArrays(1, &m_vaoID);
}

AttributePack::~AttributePack (void)
{
	clearArrays();

	if (m_programID)
		m_ctx.deleteProgram(m_programID);

	if (m_program)
		delete m_program;

	if (m_useVao)
		m_ctx.deleteVertexArrays(1, &m_vaoID);
}

AttributeArray* AttributePack::getArray (int i)
{
	return m_arrays.at(i);
}

int AttributePack::getArrayCount (void)
{
	return (int)m_arrays.size();
}

void AttributePack::newArray (DrawTestSpec::Storage storage)
{
	m_arrays.push_back(new AttributeArray(storage, m_ctx));
}

void AttributePack::clearArrays (void)
{
	for (std::vector<AttributeArray*>::iterator itr = m_arrays.begin(); itr != m_arrays.end(); itr++)
		delete *itr;
	m_arrays.clear();
}

void AttributePack::updateProgram (void)
{
	if (m_programID)
		m_ctx.deleteProgram(m_programID);
	if (m_program)
		delete m_program;

	m_program = new DrawTestShaderProgram(m_renderCtx, m_arrays);
	m_programID = m_ctx.createProgram(m_program);
}

void AttributePack::render (DrawTestSpec::Primitive primitive, DrawTestSpec::DrawMethod drawMethod, int firstVertex, int vertexCount, DrawTestSpec::IndexType indexType, const void* indexOffset, int rangeStart, int rangeEnd, int instanceCount, int indirectOffset, int baseVertex, float coordScale, float colorScale, AttributeArray* indexArray)
{
	DE_ASSERT(m_program != DE_NULL);
	DE_ASSERT(m_programID != 0);

	m_ctx.viewport(0, 0, m_screen.getWidth(), m_screen.getHeight());
	m_ctx.clearColor(0.0, 0.0, 0.0, 1.0);
	m_ctx.clear(GL_COLOR_BUFFER_BIT);

	m_ctx.useProgram(m_programID);
	GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glUseProgram()");

	m_ctx.uniform1f(m_ctx.getUniformLocation(m_programID, "u_coordScale"), coordScale);
	m_ctx.uniform1f(m_ctx.getUniformLocation(m_programID, "u_colorScale"), colorScale);

	if (m_useVao)
		m_ctx.bindVertexArray(m_vaoID);

	if (indexArray)
		indexArray->bindIndexArray(DrawTestSpec::TARGET_ELEMENT_ARRAY);

	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
	{
		std::stringstream attribName;
		attribName << "a_" << arrayNdx;

		deUint32 loc = m_ctx.getAttribLocation(m_programID, attribName.str().c_str());

		if (m_arrays[arrayNdx]->isBound())
		{
			m_ctx.enableVertexAttribArray(loc);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glEnableVertexAttribArray()");
		}

		m_arrays[arrayNdx]->bindAttribute(loc);
	}

	if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS)
	{
		m_ctx.drawArrays(primitiveToGL(primitive), firstVertex, vertexCount);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArrays()");
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INSTANCED)
	{
		m_ctx.drawArraysInstanced(primitiveToGL(primitive), firstVertex, vertexCount, instanceCount);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysInstanced()");
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS)
	{
		m_ctx.drawElements(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElements()");
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED)
	{
		m_ctx.drawRangeElements(primitiveToGL(primitive), rangeStart, rangeEnd, vertexCount, indexTypeToGL(indexType), indexOffset);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawRangeElements()");
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED)
	{
		m_ctx.drawElementsInstanced(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, instanceCount);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsInstanced()");
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWARRAYS_INDIRECT)
	{
		struct DrawCommand
		{
			GLuint count;
			GLuint primCount;
			GLuint first;
			GLuint reservedMustBeZero;
		};
		deUint8* buffer = new deUint8[sizeof(DrawCommand) + indirectOffset];

		{
			DrawCommand command;

			command.count				= vertexCount;
			command.primCount			= instanceCount;
			command.first				= firstVertex;
			command.reservedMustBeZero	= 0;

			memcpy(buffer + indirectOffset, &command, sizeof(command));

			if (m_logEnabled)
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "DrawArraysIndirectCommand:\n"
					<< "\tcount: " << command.count << "\n"
					<< "\tprimCount: " << command.primCount << "\n"
					<< "\tfirst: " << command.first << "\n"
					<< "\treservedMustBeZero: " << command.reservedMustBeZero << "\n"
					<< tcu::TestLog::EndMessage;
		}

		GLuint indirectBuf = 0;
		m_ctx.genBuffers(1, &indirectBuf);
		m_ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuf);
		m_ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand) + indirectOffset, buffer, GL_STATIC_DRAW);
		delete [] buffer;

		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "Setup draw indirect buffer");

		m_ctx.drawArraysIndirect(primitiveToGL(primitive), glu::BufferOffsetAsPointer(indirectOffset));
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysIndirect()");

		m_ctx.deleteBuffers(1, &indirectBuf);
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INDIRECT)
	{
		struct DrawCommand
		{
			GLuint count;
			GLuint primCount;
			GLuint firstIndex;
			GLint  baseVertex;
			GLuint reservedMustBeZero;
		};
		deUint8* buffer = new deUint8[sizeof(DrawCommand) + indirectOffset];

		{
			DrawCommand command;

			// index offset must be converted to firstIndex by dividing with the index element size
			DE_ASSERT(((const deUint8*)indexOffset - (const deUint8*)DE_NULL) % gls::DrawTestSpec::indexTypeSize(indexType) == 0); // \note This is checked in spec validation

			command.count				= vertexCount;
			command.primCount			= instanceCount;
			command.firstIndex			= (glw::GLuint)(((const deUint8*)indexOffset - (const deUint8*)DE_NULL) / gls::DrawTestSpec::indexTypeSize(indexType));
			command.baseVertex			= baseVertex;
			command.reservedMustBeZero	= 0;

			memcpy(buffer + indirectOffset, &command, sizeof(command));

			if (m_logEnabled)
				m_testCtx.getLog()
					<< tcu::TestLog::Message
					<< "DrawElementsIndirectCommand:\n"
					<< "\tcount: " << command.count << "\n"
					<< "\tprimCount: " << command.primCount << "\n"
					<< "\tfirstIndex: " << command.firstIndex << "\n"
					<< "\tbaseVertex: " << command.baseVertex << "\n"
					<< "\treservedMustBeZero: " << command.reservedMustBeZero << "\n"
					<< tcu::TestLog::EndMessage;
		}

		GLuint indirectBuf = 0;
		m_ctx.genBuffers(1, &indirectBuf);
		m_ctx.bindBuffer(GL_DRAW_INDIRECT_BUFFER, indirectBuf);
		m_ctx.bufferData(GL_DRAW_INDIRECT_BUFFER, sizeof(DrawCommand) + indirectOffset, buffer, GL_STATIC_DRAW);
		delete [] buffer;

		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "Setup draw indirect buffer");

		m_ctx.drawElementsIndirect(primitiveToGL(primitive), indexTypeToGL(indexType), glu::BufferOffsetAsPointer(indirectOffset));
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawArraysIndirect()");

		m_ctx.deleteBuffers(1, &indirectBuf);
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_BASEVERTEX)
	{
		m_ctx.drawElementsBaseVertex(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, baseVertex);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsBaseVertex()");
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX)
	{
		m_ctx.drawElementsInstancedBaseVertex(primitiveToGL(primitive), vertexCount, indexTypeToGL(indexType), indexOffset, instanceCount, baseVertex);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawElementsInstancedBaseVertex()");
	}
	else if (drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX)
	{
		m_ctx.drawRangeElementsBaseVertex(primitiveToGL(primitive), rangeStart, rangeEnd, vertexCount, indexTypeToGL(indexType), indexOffset, baseVertex);
		GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDrawRangeElementsBaseVertex()");
	}
	else
		DE_ASSERT(DE_FALSE);

	for (int arrayNdx = 0; arrayNdx < (int)m_arrays.size(); arrayNdx++)
	{
		if (m_arrays[arrayNdx]->isBound())
		{
			std::stringstream attribName;
			attribName << "a_" << arrayNdx;

			deUint32 loc = m_ctx.getAttribLocation(m_programID, attribName.str().c_str());

			m_ctx.disableVertexAttribArray(loc);
			GLU_EXPECT_NO_ERROR(m_ctx.getError(), "glDisableVertexAttribArray()");
		}
	}

	if (m_useVao)
		m_ctx.bindVertexArray(0);

	m_ctx.useProgram(0);
	m_ctx.readPixels(m_screen, 0, 0, m_screen.getWidth(), m_screen.getHeight());
}

// DrawTestSpec

DrawTestSpec::AttributeSpec	DrawTestSpec::AttributeSpec::createAttributeArray (InputType inputType, OutputType outputType, Storage storage, Usage usage, int componentCount, int offset, int stride, bool normalize, int instanceDivisor)
{
	DrawTestSpec::AttributeSpec spec;

	spec.inputType			= inputType;
	spec.outputType			= outputType;
	spec.storage			= storage;
	spec.usage				= usage;
	spec.componentCount		= componentCount;
	spec.offset				= offset;
	spec.stride				= stride;
	spec.normalize			= normalize;
	spec.instanceDivisor	= instanceDivisor;

	spec.useDefaultAttribute= false;

	return spec;
}

DrawTestSpec::AttributeSpec	DrawTestSpec::AttributeSpec::createDefaultAttribute (InputType inputType, OutputType outputType, int componentCount)
{
	DE_ASSERT(inputType == INPUTTYPE_INT || inputType == INPUTTYPE_UNSIGNED_INT || inputType == INPUTTYPE_FLOAT);
	DE_ASSERT(inputType == INPUTTYPE_FLOAT || componentCount == 4);

	DrawTestSpec::AttributeSpec spec;

	spec.inputType				= inputType;
	spec.outputType				= outputType;
	spec.storage				= DrawTestSpec::STORAGE_LAST;
	spec.usage					= DrawTestSpec::USAGE_LAST;
	spec.componentCount			= componentCount;
	spec.offset					= 0;
	spec.stride					= 0;
	spec.normalize				= 0;
	spec.instanceDivisor		= 0;

	spec.useDefaultAttribute	= true;

	return spec;
}

DrawTestSpec::AttributeSpec::AttributeSpec (void)
{
	inputType					= DrawTestSpec::INPUTTYPE_LAST;
	outputType					= DrawTestSpec::OUTPUTTYPE_LAST;
	storage						= DrawTestSpec::STORAGE_LAST;
	usage						= DrawTestSpec::USAGE_LAST;
	componentCount				= 0;
	offset						= 0;
	stride						= 0;
	normalize					= false;
	instanceDivisor				= 0;
	useDefaultAttribute			= false;
	additionalPositionAttribute = false;
	bgraComponentOrder			= false;
}

int DrawTestSpec::AttributeSpec::hash (void) const
{
	if (useDefaultAttribute)
	{
		return 1 * int(inputType) + 7 * int(outputType) + 13 * componentCount;
	}
	else
	{
		return 1 * int(inputType) + 2 * int(outputType) + 3 * int(storage) + 5 * int(usage) + 7 * componentCount + 11 * offset + 13 * stride + 17 * (normalize ? 0 : 1) + 19 * instanceDivisor;
	}
}

bool DrawTestSpec::AttributeSpec::valid (glu::ApiType ctxType) const
{
	const bool inputTypeFloat				= inputType == DrawTestSpec::INPUTTYPE_FLOAT || inputType  == DrawTestSpec::INPUTTYPE_FIXED || inputType == DrawTestSpec::INPUTTYPE_HALF;
	const bool inputTypeUnsignedInteger		= inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE || inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT || inputType  == DrawTestSpec::INPUTTYPE_UNSIGNED_INT || inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10;
	const bool inputTypeSignedInteger		= inputType == DrawTestSpec::INPUTTYPE_BYTE  || inputType == DrawTestSpec::INPUTTYPE_SHORT || inputType == DrawTestSpec::INPUTTYPE_INT || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;
	const bool inputTypePacked				= inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;

	const bool outputTypeFloat				= outputType == DrawTestSpec::OUTPUTTYPE_FLOAT || outputType == DrawTestSpec::OUTPUTTYPE_VEC2  || outputType == DrawTestSpec::OUTPUTTYPE_VEC3  || outputType == DrawTestSpec::OUTPUTTYPE_VEC4;
	const bool outputTypeSignedInteger		= outputType == DrawTestSpec::OUTPUTTYPE_INT   || outputType == DrawTestSpec::OUTPUTTYPE_IVEC2 || outputType == DrawTestSpec::OUTPUTTYPE_IVEC3 || outputType == DrawTestSpec::OUTPUTTYPE_IVEC4;
	const bool outputTypeUnsignedInteger	= outputType == DrawTestSpec::OUTPUTTYPE_UINT  || outputType == DrawTestSpec::OUTPUTTYPE_UVEC2 || outputType == DrawTestSpec::OUTPUTTYPE_UVEC3 || outputType == DrawTestSpec::OUTPUTTYPE_UVEC4;

	if (useDefaultAttribute)
	{
		if (inputType != DrawTestSpec::INPUTTYPE_INT && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT && inputType != DrawTestSpec::INPUTTYPE_FLOAT)
			return false;

		if (inputType != DrawTestSpec::INPUTTYPE_FLOAT && componentCount != 4)
			return false;

		// no casting allowed (undefined results)
		if (inputType == DrawTestSpec::INPUTTYPE_INT && !outputTypeSignedInteger)
			return false;
		if (inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT && !outputTypeUnsignedInteger)
			return false;
	}

	if (inputTypePacked && componentCount != 4)
		return false;

	// Invalid conversions:

	// float -> [u]int
	if (inputTypeFloat && !outputTypeFloat)
		return false;

	// uint -> int		(undefined results)
	if (inputTypeUnsignedInteger && outputTypeSignedInteger)
		return false;

	// int -> uint		(undefined results)
	if (inputTypeSignedInteger && outputTypeUnsignedInteger)
		return false;

	// packed -> non-float (packed formats are converted to floats)
	if (inputTypePacked && !outputTypeFloat)
		return false;

	// Invalid normalize. Normalize is only valid if output type is float
	if (normalize && !outputTypeFloat)
		return false;

	// Allow reverse order (GL_BGRA) only for packed and 4-component ubyte
	if (bgraComponentOrder && componentCount != 4)
		return false;
	if (bgraComponentOrder && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && inputType != DrawTestSpec::INPUTTYPE_INT_2_10_10_10 && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE)
		return false;
	if (bgraComponentOrder && normalize != true)
		return false;

	// GLES2 limits
	if (ctxType == glu::ApiType::es(2,0))
	{
		if (inputType != DrawTestSpec::INPUTTYPE_FLOAT && inputType != DrawTestSpec::INPUTTYPE_FIXED &&
			inputType != DrawTestSpec::INPUTTYPE_BYTE  && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_BYTE &&
			inputType != DrawTestSpec::INPUTTYPE_SHORT && inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_SHORT)
			return false;

		if (!outputTypeFloat)
			return false;

		if (bgraComponentOrder)
			return false;
	}

	// GLES3 limits
	if (ctxType.getProfile() == glu::PROFILE_ES && ctxType.getMajorVersion() == 3)
	{
		if (bgraComponentOrder)
			return false;
	}

	// No user pointers in GL core
	if (ctxType.getProfile() == glu::PROFILE_CORE)
	{
		if (!useDefaultAttribute && storage == DrawTestSpec::STORAGE_USER)
			return false;
	}

	return true;
}

bool DrawTestSpec::AttributeSpec::isBufferAligned (void) const
{
	const bool inputTypePacked = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;

	// Buffer alignment, offset is a multiple of underlying data type size?
	if (storage == STORAGE_BUFFER)
	{
		int dataTypeSize = gls::DrawTestSpec::inputTypeSize(inputType);
		if (inputTypePacked)
			dataTypeSize = 4;

		if (offset % dataTypeSize != 0)
			return false;
	}

	return true;
}

bool DrawTestSpec::AttributeSpec::isBufferStrideAligned (void) const
{
	const bool inputTypePacked = inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 || inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10;

	// Buffer alignment, offset is a multiple of underlying data type size?
	if (storage == STORAGE_BUFFER)
	{
		int dataTypeSize = gls::DrawTestSpec::inputTypeSize(inputType);
		if (inputTypePacked)
			dataTypeSize = 4;

		if (stride % dataTypeSize != 0)
			return false;
	}

	return true;
}

std::string DrawTestSpec::targetToString(Target target)
{
	static const char* targets[] =
	{
		"element_array",	// TARGET_ELEMENT_ARRAY = 0,
		"array"				// TARGET_ARRAY,
	};

	return de::getSizedArrayElement<DrawTestSpec::TARGET_LAST>(targets, (int)target);
}

std::string DrawTestSpec::inputTypeToString(InputType type)
{
	static const char* types[] =
	{
		"float",			// INPUTTYPE_FLOAT = 0,
		"fixed",			// INPUTTYPE_FIXED,
		"double",			// INPUTTYPE_DOUBLE

		"byte",				// INPUTTYPE_BYTE,
		"short",			// INPUTTYPE_SHORT,

		"unsigned_byte",	// INPUTTYPE_UNSIGNED_BYTE,
		"unsigned_short",	// INPUTTYPE_UNSIGNED_SHORT,

		"int",						// INPUTTYPE_INT,
		"unsigned_int",				// INPUTTYPE_UNSIGNED_INT,
		"half",						// INPUTTYPE_HALF,
		"unsigned_int2_10_10_10",	// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
		"int2_10_10_10"				// INPUTTYPE_INT_2_10_10_10,
	};

	return de::getSizedArrayElement<DrawTestSpec::INPUTTYPE_LAST>(types, (int)type);
}

std::string DrawTestSpec::outputTypeToString(OutputType type)
{
	static const char* types[] =
	{
		"float",		// OUTPUTTYPE_FLOAT = 0,
		"vec2",			// OUTPUTTYPE_VEC2,
		"vec3",			// OUTPUTTYPE_VEC3,
		"vec4",			// OUTPUTTYPE_VEC4,

		"int",			// OUTPUTTYPE_INT,
		"uint",			// OUTPUTTYPE_UINT,

		"ivec2",		// OUTPUTTYPE_IVEC2,
		"ivec3",		// OUTPUTTYPE_IVEC3,
		"ivec4",		// OUTPUTTYPE_IVEC4,

		"uvec2",		// OUTPUTTYPE_UVEC2,
		"uvec3",		// OUTPUTTYPE_UVEC3,
		"uvec4",		// OUTPUTTYPE_UVEC4,
	};

	return de::getSizedArrayElement<DrawTestSpec::OUTPUTTYPE_LAST>(types, (int)type);
}

std::string DrawTestSpec::usageTypeToString(Usage usage)
{
	static const char* usages[] =
	{
		"dynamic_draw",	// USAGE_DYNAMIC_DRAW = 0,
		"static_draw",	// USAGE_STATIC_DRAW,
		"stream_draw",	// USAGE_STREAM_DRAW,

		"stream_read",	// USAGE_STREAM_READ,
		"stream_copy",	// USAGE_STREAM_COPY,

		"static_read",	// USAGE_STATIC_READ,
		"static_copy",	// USAGE_STATIC_COPY,

		"dynamic_read",	// USAGE_DYNAMIC_READ,
		"dynamic_copy",	// USAGE_DYNAMIC_COPY,
	};

	return de::getSizedArrayElement<DrawTestSpec::USAGE_LAST>(usages, (int)usage);
}

std::string	DrawTestSpec::storageToString (Storage storage)
{
	static const char* storages[] =
	{
		"user_ptr",	// STORAGE_USER = 0,
		"buffer"	// STORAGE_BUFFER,
	};

	return de::getSizedArrayElement<DrawTestSpec::STORAGE_LAST>(storages, (int)storage);
}

std::string DrawTestSpec::primitiveToString (Primitive primitive)
{
	static const char* primitives[] =
	{
		"points",					// PRIMITIVE_POINTS ,
		"triangles",				// PRIMITIVE_TRIANGLES,
		"triangle_fan",				// PRIMITIVE_TRIANGLE_FAN,
		"triangle_strip",			// PRIMITIVE_TRIANGLE_STRIP,
		"lines",					// PRIMITIVE_LINES
		"line_strip",				// PRIMITIVE_LINE_STRIP
		"line_loop",				// PRIMITIVE_LINE_LOOP
		"lines_adjacency",			// PRIMITIVE_LINES_ADJACENCY
		"line_strip_adjacency",		// PRIMITIVE_LINE_STRIP_ADJACENCY
		"triangles_adjacency",		// PRIMITIVE_TRIANGLES_ADJACENCY
		"triangle_strip_adjacency",	// PRIMITIVE_TRIANGLE_STRIP_ADJACENCY
	};

	return de::getSizedArrayElement<DrawTestSpec::PRIMITIVE_LAST>(primitives, (int)primitive);
}

std::string DrawTestSpec::indexTypeToString (IndexType type)
{
	static const char* indexTypes[] =
	{
		"byte",		// INDEXTYPE_BYTE = 0,
		"short",	// INDEXTYPE_SHORT,
		"int",		// INDEXTYPE_INT,
	};

	return de::getSizedArrayElement<DrawTestSpec::INDEXTYPE_LAST>(indexTypes, (int)type);
}

std::string DrawTestSpec::drawMethodToString (DrawTestSpec::DrawMethod method)
{
	static const char* methods[] =
	{
		"draw_arrays",							//!< DRAWMETHOD_DRAWARRAYS
		"draw_arrays_instanced",				//!< DRAWMETHOD_DRAWARRAYS_INSTANCED
		"draw_arrays_indirect",					//!< DRAWMETHOD_DRAWARRAYS_INDIRECT
		"draw_elements",						//!< DRAWMETHOD_DRAWELEMENTS
		"draw_range_elements",					//!< DRAWMETHOD_DRAWELEMENTS_RANGED
		"draw_elements_instanced",				//!< DRAWMETHOD_DRAWELEMENTS_INSTANCED
		"draw_elements_indirect",				//!< DRAWMETHOD_DRAWELEMENTS_INDIRECT
		"draw_elements_base_vertex",			//!< DRAWMETHOD_DRAWELEMENTS_BASEVERTEX,
		"draw_elements_instanced_base_vertex",	//!< DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX,
		"draw_range_elements_base_vertex",		//!< DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX,
	};

	return de::getSizedArrayElement<DrawTestSpec::DRAWMETHOD_LAST>(methods, (int)method);
}

int DrawTestSpec::inputTypeSize (InputType type)
{
	static const int size[] =
	{
		(int)sizeof(float),			// INPUTTYPE_FLOAT = 0,
		(int)sizeof(deInt32),		// INPUTTYPE_FIXED,
		(int)sizeof(double),		// INPUTTYPE_DOUBLE

		(int)sizeof(deInt8),		// INPUTTYPE_BYTE,
		(int)sizeof(deInt16),		// INPUTTYPE_SHORT,

		(int)sizeof(deUint8),		// INPUTTYPE_UNSIGNED_BYTE,
		(int)sizeof(deUint16),		// INPUTTYPE_UNSIGNED_SHORT,

		(int)sizeof(deInt32),		// INPUTTYPE_INT,
		(int)sizeof(deUint32),		// INPUTTYPE_UNSIGNED_INT,
		(int)sizeof(deFloat16),		// INPUTTYPE_HALF,
		(int)sizeof(deUint32) / 4,	// INPUTTYPE_UNSIGNED_INT_2_10_10_10,
		(int)sizeof(deUint32) / 4	// INPUTTYPE_INT_2_10_10_10,
	};

	return de::getSizedArrayElement<DrawTestSpec::INPUTTYPE_LAST>(size, (int)type);
}

int DrawTestSpec::indexTypeSize (IndexType type)
{
	static const int size[] =
	{
		sizeof(deUint8),	// INDEXTYPE_BYTE,
		sizeof(deUint16),	// INDEXTYPE_SHORT,
		sizeof(deUint32),	// INDEXTYPE_INT,
	};

	return de::getSizedArrayElement<DrawTestSpec::INDEXTYPE_LAST>(size, (int)type);
}

std::string DrawTestSpec::getName (void) const
{
	const MethodInfo	methodInfo	= getMethodInfo(drawMethod);
	const bool			hasFirst	= methodInfo.first;
	const bool			instanced	= methodInfo.instanced;
	const bool			ranged		= methodInfo.ranged;
	const bool			indexed		= methodInfo.indexed;

	std::stringstream name;

	for (size_t ndx = 0; ndx < attribs.size(); ++ndx)
	{
		const AttributeSpec& attrib = attribs[ndx];

		if (attribs.size() > 1)
			name << "attrib" << ndx << "_";

		if (ndx == 0|| attrib.additionalPositionAttribute)
			name << "pos_";
		else
			name << "col_";

		if (attrib.useDefaultAttribute)
		{
			name
				<< "non_array_"
				<< DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "_"
				<< attrib.componentCount << "_"
				<< DrawTestSpec::outputTypeToString(attrib.outputType) << "_";
		}
		else
		{
			name
				<< DrawTestSpec::storageToString(attrib.storage) << "_"
				<< attrib.offset << "_"
				<< attrib.stride << "_"
				<< DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType);
			if (attrib.inputType != DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10 && attrib.inputType != DrawTestSpec::INPUTTYPE_INT_2_10_10_10)
				name << attrib.componentCount;
			name
				<< "_"
				<< (attrib.normalize ? "normalized_" : "")
				<< DrawTestSpec::outputTypeToString(attrib.outputType) << "_"
				<< DrawTestSpec::usageTypeToString(attrib.usage) << "_"
				<< attrib.instanceDivisor << "_";
		}
	}

	if (indexed)
		name
			<< "index_" << DrawTestSpec::indexTypeToString(indexType) << "_"
			<< DrawTestSpec::storageToString(indexStorage) << "_"
			<< "offset" << indexPointerOffset << "_";
	if (hasFirst)
		name << "first" << first << "_";
	if (ranged)
		name << "ranged_" << indexMin << "_" << indexMax << "_";
	if (instanced)
		name << "instances" << instanceCount << "_";

	switch (primitive)
	{
		case DrawTestSpec::PRIMITIVE_POINTS:
			name << "points_";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLES:
			name << "triangles_";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
			name << "triangle_fan_";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
			name << "triangle_strip_";
			break;
		case DrawTestSpec::PRIMITIVE_LINES:
			name << "lines_";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP:
			name << "line_strip_";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_LOOP:
			name << "line_loop_";
			break;
		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
			name << "line_adjancency";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
			name << "line_strip_adjancency";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
			name << "triangles_adjancency";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
			name << "triangle_strip_adjancency";
			break;
		default:
			DE_ASSERT(false);
			break;
	}

	name << primitiveCount;

	return name.str();
}

std::string DrawTestSpec::getDesc (void) const
{
	std::stringstream desc;

	for (size_t ndx = 0; ndx < attribs.size(); ++ndx)
	{
		const AttributeSpec& attrib = attribs[ndx];

		if (attrib.useDefaultAttribute)
		{
			desc
				<< "Attribute " << ndx << ": default, " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position ,") : ("color ,"))
				<< "input datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << ", "
				<< "input component count " << attrib.componentCount << ", "
				<< "used as " << DrawTestSpec::outputTypeToString(attrib.outputType) << ", ";
		}
		else
		{
			desc
				<< "Attribute " << ndx << ": " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position ,") : ("color ,"))
				<< "Storage in " << DrawTestSpec::storageToString(attrib.storage) << ", "
				<< "stride " << attrib.stride << ", "
				<< "input datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << ", "
				<< "input component count " << attrib.componentCount << ", "
				<< (attrib.normalize ? "normalized, " : "")
				<< "used as " << DrawTestSpec::outputTypeToString(attrib.outputType) << ", "
				<< "instance divisor " << attrib.instanceDivisor << ", ";
		}
	}

	if (drawMethod == DRAWMETHOD_DRAWARRAYS)
	{
		desc
			<< "drawArrays(), "
			<< "first " << first << ", ";
	}
	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INSTANCED)
	{
		desc
			<< "drawArraysInstanced(), "
			<< "first " << first << ", "
			<< "instance count " << instanceCount << ", ";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS)
	{
		desc
			<< "drawElements(), "
			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
			<< "index offset " << indexPointerOffset << ", ";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED)
	{
		desc
			<< "drawElementsRanged(), "
			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
			<< "index offset " << indexPointerOffset << ", "
			<< "range start " << indexMin << ", "
			<< "range end " << indexMax << ", ";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED)
	{
		desc
			<< "drawElementsInstanced(), "
			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
			<< "index offset " << indexPointerOffset << ", "
			<< "instance count " << instanceCount << ", ";
	}
	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INDIRECT)
	{
		desc
			<< "drawArraysIndirect(), "
			<< "first " << first << ", "
			<< "instance count " << instanceCount << ", "
			<< "indirect offset " << indirectOffset << ", ";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT)
	{
		desc
			<< "drawElementsIndirect(), "
			<< "index type " << DrawTestSpec::indexTypeToString(indexType) << ", "
			<< "index storage in " << DrawTestSpec::storageToString(indexStorage) << ", "
			<< "index offset " << indexPointerOffset << ", "
			<< "instance count " << instanceCount << ", "
			<< "indirect offset " << indirectOffset << ", "
			<< "base vertex " << baseVertex << ", ";
	}
	else
		DE_ASSERT(DE_FALSE);

	desc << primitiveCount;

	switch (primitive)
	{
		case DrawTestSpec::PRIMITIVE_POINTS:
			desc << "points";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLES:
			desc << "triangles";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
			desc << "triangles (fan)";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
			desc << "triangles (strip)";
			break;
		case DrawTestSpec::PRIMITIVE_LINES:
			desc << "lines";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP:
			desc << "lines (strip)";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_LOOP:
			desc << "lines (loop)";
			break;
		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
			desc << "lines (adjancency)";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
			desc << "lines (strip, adjancency)";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
			desc << "triangles (adjancency)";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
			desc << "triangles (strip, adjancency)";
			break;
		default:
			DE_ASSERT(false);
			break;
	}

	return desc.str();
}

std::string DrawTestSpec::getMultilineDesc (void) const
{
	std::stringstream desc;

	for (size_t ndx = 0; ndx < attribs.size(); ++ndx)
	{
		const AttributeSpec& attrib = attribs[ndx];

		if (attrib.useDefaultAttribute)
		{
			desc
				<< "Attribute " << ndx << ": default, " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position\n") : ("color\n"))
				<< "\tinput datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "\n"
				<< "\tinput component count " << attrib.componentCount << "\n"
				<< "\tused as " << DrawTestSpec::outputTypeToString(attrib.outputType) << "\n";
		}
		else
		{
			desc
				<< "Attribute " << ndx << ": " << ((ndx == 0|| attrib.additionalPositionAttribute) ? ("position\n") : ("color\n"))
				<< "\tStorage in " << DrawTestSpec::storageToString(attrib.storage) << "\n"
				<< "\tstride " << attrib.stride << "\n"
				<< "\tinput datatype " << DrawTestSpec::inputTypeToString((DrawTestSpec::InputType)attrib.inputType) << "\n"
				<< "\tinput component count " << attrib.componentCount << "\n"
				<< (attrib.normalize ? "\tnormalized\n" : "")
				<< "\tused as " << DrawTestSpec::outputTypeToString(attrib.outputType) << "\n"
				<< "\tinstance divisor " << attrib.instanceDivisor << "\n";
		}
	}

	if (drawMethod == DRAWMETHOD_DRAWARRAYS)
	{
		desc
			<< "drawArrays()\n"
			<< "\tfirst " << first << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INSTANCED)
	{
		desc
			<< "drawArraysInstanced()\n"
			<< "\tfirst " << first << "\n"
			<< "\tinstance count " << instanceCount << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS)
	{
		desc
			<< "drawElements()\n"
			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
			<< "\tindex offset " << indexPointerOffset << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED)
	{
		desc
			<< "drawElementsRanged()\n"
			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
			<< "\tindex offset " << indexPointerOffset << "\n"
			<< "\trange start " << indexMin << "\n"
			<< "\trange end " << indexMax << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED)
	{
		desc
			<< "drawElementsInstanced()\n"
			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
			<< "\tindex offset " << indexPointerOffset << "\n"
			<< "\tinstance count " << instanceCount << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWARRAYS_INDIRECT)
	{
		desc
			<< "drawArraysIndirect()\n"
			<< "\tfirst " << first << "\n"
			<< "\tinstance count " << instanceCount << "\n"
			<< "\tindirect offset " << indirectOffset << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT)
	{
		desc
			<< "drawElementsIndirect()\n"
			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
			<< "\tindex offset " << indexPointerOffset << "\n"
			<< "\tinstance count " << instanceCount << "\n"
			<< "\tindirect offset " << indirectOffset << "\n"
			<< "\tbase vertex " << baseVertex << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_BASEVERTEX)
	{
		desc
			<< "drawElementsBaseVertex()\n"
			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
			<< "\tindex offset " << indexPointerOffset << "\n"
			<< "\tbase vertex " << baseVertex << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX)
	{
		desc
			<< "drawElementsInstancedBaseVertex()\n"
			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
			<< "\tindex offset " << indexPointerOffset << "\n"
			<< "\tinstance count " << instanceCount << "\n"
			<< "\tbase vertex " << baseVertex << "\n";
	}
	else if (drawMethod == DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX)
	{
		desc
			<< "drawRangeElementsBaseVertex()\n"
			<< "\tindex type " << DrawTestSpec::indexTypeToString(indexType) << "\n"
			<< "\tindex storage in " << DrawTestSpec::storageToString(indexStorage) << "\n"
			<< "\tindex offset " << indexPointerOffset << "\n"
			<< "\tbase vertex " << baseVertex << "\n"
			<< "\trange start " << indexMin << "\n"
			<< "\trange end " << indexMax << "\n";
	}
	else
		DE_ASSERT(DE_FALSE);

	desc << "\t" << primitiveCount << " ";

	switch (primitive)
	{
		case DrawTestSpec::PRIMITIVE_POINTS:
			desc << "points";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLES:
			desc << "triangles";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
			desc << "triangles (fan)";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
			desc << "triangles (strip)";
			break;
		case DrawTestSpec::PRIMITIVE_LINES:
			desc << "lines";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP:
			desc << "lines (strip)";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_LOOP:
			desc << "lines (loop)";
			break;
		case DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
			desc << "lines (adjancency)";
			break;
		case DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
			desc << "lines (strip, adjancency)";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
			desc << "triangles (adjancency)";
			break;
		case DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
			desc << "triangles (strip, adjancency)";
			break;
		default:
			DE_ASSERT(false);
			break;
	}

	desc << "\n";

	return desc.str();
}

DrawTestSpec::DrawTestSpec (void)
{
	primitive			= PRIMITIVE_LAST;
	primitiveCount		= 0;
	drawMethod			= DRAWMETHOD_LAST;
	indexType			= INDEXTYPE_LAST;
	indexPointerOffset	= 0;
	indexStorage		= STORAGE_LAST;
	first				= 0;
	indexMin			= 0;
	indexMax			= 0;
	instanceCount		= 0;
	indirectOffset		= 0;
	baseVertex			= 0;
}

int DrawTestSpec::hash (void) const
{
	// Use only drawmode-relevant values in "hashing" as the unrelevant values might not be set (causing non-deterministic behavior).
	const MethodInfo	methodInfo		= getMethodInfo(drawMethod);
	const bool			arrayed			= methodInfo.first;
	const bool			instanced		= methodInfo.instanced;
	const bool			ranged			= methodInfo.ranged;
	const bool			indexed			= methodInfo.indexed;
	const bool			indirect		= methodInfo.indirect;
	const bool			hasBaseVtx		= methodInfo.baseVertex;

	const int			indexHash		= (!indexed)	? (0) : (int(indexType) + 10 * indexPointerOffset + 100 * int(indexStorage));
	const int			arrayHash		= (!arrayed)	? (0) : (first);
	const int			indexRangeHash	= (!ranged)		? (0) : (indexMin + 10 * indexMax);
	const int			instanceHash	= (!instanced)	? (0) : (instanceCount);
	const int			indirectHash	= (!indirect)	? (0) : (indirectOffset);
	const int			baseVtxHash		= (!hasBaseVtx)	? (0) : (baseVertex);
	const int			basicHash		= int(primitive) + 10 * primitiveCount + 100 * int(drawMethod);

	return indexHash + 3 * arrayHash + 5 * indexRangeHash + 7 * instanceHash + 13 * basicHash + 17 * (int)attribs.size() + 19 * primitiveCount + 23 * indirectHash + 27 * baseVtxHash;
}

bool DrawTestSpec::valid (void) const
{
	DE_ASSERT(apiType.getProfile() != glu::PROFILE_LAST);
	DE_ASSERT(primitive != PRIMITIVE_LAST);
	DE_ASSERT(drawMethod != DRAWMETHOD_LAST);

	const MethodInfo methodInfo = getMethodInfo(drawMethod);

	for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
		if (!attribs[ndx].valid(apiType))
			return false;

	if (methodInfo.ranged)
	{
		deUint32 maxIndexValue = 0;
		if (indexType == INDEXTYPE_BYTE)
			maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_BYTE).ub.getValue();
		else if (indexType == INDEXTYPE_SHORT)
			maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_SHORT).us.getValue();
		else if (indexType == INDEXTYPE_INT)
			maxIndexValue = GLValue::getMaxValue(INPUTTYPE_UNSIGNED_INT).ui.getValue();
		else
			DE_ASSERT(DE_FALSE);

		if (indexMin > indexMax)
			return false;
		if (indexMin < 0 || indexMax < 0)
			return false;
		if ((deUint32)indexMin > maxIndexValue || (deUint32)indexMax > maxIndexValue)
			return false;
	}

	if (methodInfo.first && first < 0)
		return false;

	// GLES2 limits
	if (apiType == glu::ApiType::es(2,0))
	{
		if (drawMethod != gls::DrawTestSpec::DRAWMETHOD_DRAWARRAYS && drawMethod != gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS)
			return false;
		if (drawMethod == gls::DrawTestSpec::DRAWMETHOD_DRAWELEMENTS && (indexType != INDEXTYPE_BYTE && indexType != INDEXTYPE_SHORT))
			return false;
	}

	// Indirect limitations
	if (methodInfo.indirect)
	{
		// Indirect offset alignment
		if (indirectOffset % 4 != 0)
			return false;

		// All attribute arrays must be stored in a buffer
		for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
			if (!attribs[ndx].useDefaultAttribute && attribs[ndx].storage == gls::DrawTestSpec::STORAGE_USER)
				return false;
	}
	if (drawMethod == DRAWMETHOD_DRAWELEMENTS_INDIRECT)
	{
		// index offset must be convertable to firstIndex
		if (indexPointerOffset % gls::DrawTestSpec::indexTypeSize(indexType) != 0)
			return false;

		// Indices must be in a buffer
		if (indexStorage != STORAGE_BUFFER)
			return false;
	}

	// Do not allow user pointer in GL core
	if (apiType.getProfile() == glu::PROFILE_CORE)
	{
		if (methodInfo.indexed && indexStorage == DrawTestSpec::STORAGE_USER)
			return false;
	}

	return true;
}

DrawTestSpec::CompatibilityTestType DrawTestSpec::isCompatibilityTest (void) const
{
	const MethodInfo methodInfo = getMethodInfo(drawMethod);

	bool bufferAlignmentBad = false;
	bool strideAlignmentBad = false;

	// Attribute buffer alignment
	for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
		if (!attribs[ndx].isBufferAligned())
			bufferAlignmentBad = true;

	// Attribute stride alignment
	for (int ndx = 0; ndx < (int)attribs.size(); ++ndx)
		if (!attribs[ndx].isBufferStrideAligned())
			strideAlignmentBad = true;

	// Index buffer alignment
	if (methodInfo.indexed)
	{
		if (indexStorage == STORAGE_BUFFER)
		{
			int indexSize = 0;
			if (indexType == INDEXTYPE_BYTE)
				indexSize = 1;
			else if (indexType == INDEXTYPE_SHORT)
				indexSize = 2;
			else if (indexType == INDEXTYPE_INT)
				indexSize = 4;
			else
				DE_ASSERT(DE_FALSE);

			if (indexPointerOffset % indexSize != 0)
				bufferAlignmentBad = true;
		}
	}

	// \note combination bad alignment & stride is treated as bad offset
	if (bufferAlignmentBad)
		return COMPATIBILITY_UNALIGNED_OFFSET;
	else if (strideAlignmentBad)
		return COMPATIBILITY_UNALIGNED_STRIDE;
	else
		return COMPATIBILITY_NONE;
}

enum PrimitiveClass
{
	PRIMITIVECLASS_POINT = 0,
	PRIMITIVECLASS_LINE,
	PRIMITIVECLASS_TRIANGLE,

	PRIMITIVECLASS_LAST
};

static PrimitiveClass getDrawPrimitiveClass (gls::DrawTestSpec::Primitive primitiveType)
{
	switch (primitiveType)
	{
		case gls::DrawTestSpec::PRIMITIVE_POINTS:
			return PRIMITIVECLASS_POINT;

		case gls::DrawTestSpec::PRIMITIVE_LINES:
		case gls::DrawTestSpec::PRIMITIVE_LINE_STRIP:
		case gls::DrawTestSpec::PRIMITIVE_LINE_LOOP:
		case gls::DrawTestSpec::PRIMITIVE_LINES_ADJACENCY:
		case gls::DrawTestSpec::PRIMITIVE_LINE_STRIP_ADJACENCY:
			return PRIMITIVECLASS_LINE;

		case gls::DrawTestSpec::PRIMITIVE_TRIANGLES:
		case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_FAN:
		case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP:
		case gls::DrawTestSpec::PRIMITIVE_TRIANGLES_ADJACENCY:
		case gls::DrawTestSpec::PRIMITIVE_TRIANGLE_STRIP_ADJACENCY:
			return PRIMITIVECLASS_TRIANGLE;

		default:
			DE_ASSERT(false);
			return PRIMITIVECLASS_LAST;
	}
}

static bool containsLineCases (const std::vector<DrawTestSpec>& m_specs)
{
	for (int ndx = 0; ndx < (int)m_specs.size(); ++ndx)
	{
		if (getDrawPrimitiveClass(m_specs[ndx].primitive) == PRIMITIVECLASS_LINE)
			return true;
	}
	return false;
}

// DrawTest

DrawTest::DrawTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const DrawTestSpec& spec, const char* name, const char* desc)
	: TestCase			(testCtx, name, desc)
	, m_renderCtx		(renderCtx)
	, m_contextInfo		(DE_NULL)
	, m_refBuffers		(DE_NULL)
	, m_refContext		(DE_NULL)
	, m_glesContext		(DE_NULL)
	, m_glArrayPack		(DE_NULL)
	, m_rrArrayPack		(DE_NULL)
	, m_maxDiffRed		(-1)
	, m_maxDiffGreen	(-1)
	, m_maxDiffBlue		(-1)
	, m_iteration		(0)
	, m_result			()	// \note no per-iteration result logging (only one iteration)
{
	addIteration(spec);
}

DrawTest::DrawTest (tcu::TestContext& testCtx, glu::RenderContext& renderCtx, const char* name, const char* desc)
	: TestCase			(testCtx, name, desc)
	, m_renderCtx		(renderCtx)
	, m_contextInfo		(DE_NULL)
	, m_refBuffers		(DE_NULL)
	, m_refContext		(DE_NULL)
	, m_glesContext		(DE_NULL)
	, m_glArrayPack		(DE_NULL)
	, m_rrArrayPack		(DE_NULL)
	, m_maxDiffRed		(-1)
	, m_maxDiffGreen	(-1)
	, m_maxDiffBlue		(-1)
	, m_iteration		(0)
	, m_result			(testCtx.getLog(), "Iteration result: ")
{
}

DrawTest::~DrawTest	(void)
{
	deinit();
}

void DrawTest::addIteration (const DrawTestSpec& spec, const char* description)
{
	// Validate spec
	const bool validSpec = spec.valid();
	DE_ASSERT(validSpec);

	if (!validSpec)
		return;

	// Check the context type is the same with other iterations
	if (!m_specs.empty())
	{
		const bool validContext = m_specs[0].apiType == spec.apiType;
		DE_ASSERT(validContext);

		if (!validContext)
			return;
	}

	m_specs.push_back(spec);

	if (description)
		m_iteration_descriptions.push_back(std::string(description));
	else
		m_iteration_descriptions.push_back(std::string());
}

void DrawTest::init (void)
{
	DE_ASSERT(!m_specs.empty());
	DE_ASSERT(contextSupports(m_renderCtx.getType(), m_specs[0].apiType));

	const int						renderTargetWidth	= de::min(MAX_RENDER_TARGET_SIZE, m_renderCtx.getRenderTarget().getWidth());
	const int						renderTargetHeight	= de::min(MAX_RENDER_TARGET_SIZE, m_renderCtx.getRenderTarget().getHeight());

	// lines have significantly different rasterization in MSAA mode
	const bool						isLineCase			= containsLineCases(m_specs);
	const bool						isMSAACase			= m_renderCtx.getRenderTarget().getNumSamples() > 1;
	const int						renderTargetSamples	= (isMSAACase && isLineCase) ? (4) : (1);

	sglr::ReferenceContextLimits	limits				(m_renderCtx);
	bool							useVao				= false;

	m_glesContext = new sglr::GLContext(m_renderCtx, m_testCtx.getLog(), sglr::GLCONTEXT_LOG_CALLS | sglr::GLCONTEXT_LOG_PROGRAMS, tcu::IVec4(0, 0, renderTargetWidth, renderTargetHeight));

	if (m_renderCtx.getType().getAPI() == glu::ApiType::es(2,0) || m_renderCtx.getType().getAPI() == glu::ApiType::es(3,0))
		useVao = false;
	else if (contextSupports(m_renderCtx.getType(), glu::ApiType::es(3,1)) || glu::isContextTypeGLCore(m_renderCtx.getType()))
		useVao = true;
	else
		DE_FATAL("Unknown context type");

	m_refBuffers	= new sglr::ReferenceContextBuffers(m_renderCtx.getRenderTarget().getPixelFormat(), 0, 0, renderTargetWidth, renderTargetHeight, renderTargetSamples);
	m_refContext	= new sglr::ReferenceContext(limits, m_refBuffers->getColorbuffer(), m_refBuffers->getDepthbuffer(), m_refBuffers->getStencilbuffer());

	m_glArrayPack	= new AttributePack(m_testCtx, m_renderCtx, *m_glesContext, tcu::UVec2(renderTargetWidth, renderTargetHeight), useVao, true);
	m_rrArrayPack	= new AttributePack(m_testCtx, m_renderCtx, *m_refContext,  tcu::UVec2(renderTargetWidth, renderTargetHeight), useVao, false);

	m_maxDiffRed	= deCeilFloatToInt32(256.0f * (6.0f / (float)(1 << m_renderCtx.getRenderTarget().getPixelFormat().redBits)));
	m_maxDiffGreen	= deCeilFloatToInt32(256.0f * (6.0f / (float)(1 << m_renderCtx.getRenderTarget().getPixelFormat().greenBits)));
	m_maxDiffBlue	= deCeilFloatToInt32(256.0f * (6.0f / (float)(1 << m_renderCtx.getRenderTarget().getPixelFormat().blueBits)));
	m_contextInfo	= glu::ContextInfo::create(m_renderCtx);
}

void DrawTest::deinit (void)
{
	delete m_glArrayPack;
	delete m_rrArrayPack;
	delete m_refBuffers;
	delete m_refContext;
	delete m_glesContext;
	delete m_contextInfo;

	m_glArrayPack	= DE_NULL;
	m_rrArrayPack	= DE_NULL;
	m_refBuffers	= DE_NULL;
	m_refContext	= DE_NULL;
	m_glesContext	= DE_NULL;
	m_contextInfo	= DE_NULL;
}

DrawTest::IterateResult DrawTest::iterate (void)
{
	const int					specNdx			= (m_iteration / 2);
	const DrawTestSpec&			spec			= m_specs[specNdx];

	if (spec.drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_BASEVERTEX ||
		spec.drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_INSTANCED_BASEVERTEX ||
		spec.drawMethod == DrawTestSpec::DRAWMETHOD_DRAWELEMENTS_RANGED_BASEVERTEX)
	{
		const bool supportsES32orGL45 = contextSupports(m_renderCtx.getType(), glu::ApiType::es(3, 2)) ||
										contextSupports(m_renderCtx.getType(), glu::ApiType::core(4, 5));
		TCU_CHECK_AND_THROW(NotSupportedError, supportsES32orGL45 || m_contextInfo->isExtensionSupported("GL_EXT_draw_elements_base_vertex"), "GL_EXT_draw_elements_base_vertex is not supported.");
	}

	const bool					drawStep		= (m_iteration % 2) == 0;
	const bool					compareStep		= (m_iteration % 2) == 1;
	const IterateResult			iterateResult	= ((size_t)m_iteration + 1 == m_specs.size()*2) ? (STOP) : (CONTINUE);
	const bool					updateProgram	= (m_iteration == 0) || (drawStep && !checkSpecsShaderCompatible(m_specs[specNdx], m_specs[specNdx-1])); // try to use the same shader in all iterations
	IterationLogSectionEmitter	sectionEmitter	(m_testCtx.getLog(), specNdx, m_specs.size(), m_iteration_descriptions[specNdx], drawStep && m_specs.size()!=1);

	if (drawStep)
	{
		const MethodInfo	methodInfo				= getMethodInfo(spec.drawMethod);
		const bool			indexed					= methodInfo.indexed;
		const bool			instanced				= methodInfo.instanced;
		const bool			ranged					= methodInfo.ranged;
		const bool			hasFirst				= methodInfo.first;
		const bool			hasBaseVtx				= methodInfo.baseVertex;

		const size_t		primitiveElementCount	= getElementCount(spec.primitive, spec.primitiveCount);						// !< elements to be drawn
		const int			indexMin				= (ranged) ? (spec.indexMin) : (0);
		const int			firstAddition			= (hasFirst) ? (spec.first) : (0);
		const int			baseVertexAddition		= (hasBaseVtx && spec.baseVertex > 0) ? ( spec.baseVertex) : (0);			// spec.baseVertex > 0 => Create bigger attribute buffer
		const int			indexBase				= (hasBaseVtx && spec.baseVertex < 0) ? (-spec.baseVertex) : (0);			// spec.baseVertex < 0 => Create bigger indices
		const size_t		elementCount			= primitiveElementCount + indexMin + firstAddition + baseVertexAddition;	// !< elements in buffer (buffer should have at least primitiveElementCount ACCESSIBLE (index range, first) elements)
		const int			maxElementIndex			= (int)primitiveElementCount + indexMin + firstAddition - 1;
		const int			indexMax				= de::max(0, (ranged) ? (de::clamp<int>(spec.indexMax, 0, maxElementIndex)) : (maxElementIndex));
		float				coordScale				= getCoordScale(spec);
		float				colorScale				= getColorScale(spec);

		rr::GenericVec4		nullAttribValue;

		// Log info
		m_testCtx.getLog() << TestLog::Message << spec.getMultilineDesc() << TestLog::EndMessage;
		m_testCtx.getLog() << TestLog::Message << TestLog::EndMessage; // extra line for clarity

		// Data

		m_glArrayPack->clearArrays();
		m_rrArrayPack->clearArrays();

		for (int attribNdx = 0; attribNdx < (int)spec.attribs.size(); attribNdx++)
		{
			DrawTestSpec::AttributeSpec attribSpec		= spec.attribs[attribNdx];
			const bool					isPositionAttr	= (attribNdx == 0) || (attribSpec.additionalPositionAttribute);

			if (attribSpec.useDefaultAttribute)
			{
				const int		seed		= 10 * attribSpec.hash() + 100 * spec.hash() + attribNdx;
				rr::GenericVec4 attribValue = RandomArrayGenerator::generateAttributeValue(seed, attribSpec.inputType);

				m_glArrayPack->newArray(DrawTestSpec::STORAGE_USER);
				m_rrArrayPack->newArray(DrawTestSpec::STORAGE_USER);

				m_glArrayPack->getArray(attribNdx)->setupArray(false, 0, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, false, 0, 0, attribValue, isPositionAttr, false);
				m_rrArrayPack->getArray(attribNdx)->setupArray(false, 0, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, false, 0, 0, attribValue, isPositionAttr, false);
			}
			else
			{
				const int					seed					= attribSpec.hash() + 100 * spec.hash() + attribNdx;
				const size_t				elementSize				= attribSpec.componentCount * DrawTestSpec::inputTypeSize(attribSpec.inputType);
				const size_t				stride					= (attribSpec.stride == 0) ? (elementSize) : (attribSpec.stride);
				const size_t				evaluatedElementCount	= (instanced && attribSpec.instanceDivisor > 0) ? (spec.instanceCount / attribSpec.instanceDivisor + 1) : (elementCount);
				const size_t				referencedElementCount	= (ranged) ? (de::max<size_t>(evaluatedElementCount, spec.indexMax + 1)) : (evaluatedElementCount);
				const size_t				bufferSize				= attribSpec.offset + stride * (referencedElementCount - 1) + elementSize;
				const char*					data					= RandomArrayGenerator::generateArray(seed, (int)referencedElementCount, attribSpec.componentCount, attribSpec.offset, (int)stride, attribSpec.inputType);

				try
				{
					m_glArrayPack->newArray(attribSpec.storage);
					m_rrArrayPack->newArray(attribSpec.storage);

					m_glArrayPack->getArray(attribNdx)->data(DrawTestSpec::TARGET_ARRAY, bufferSize, data, attribSpec.usage);
					m_rrArrayPack->getArray(attribNdx)->data(DrawTestSpec::TARGET_ARRAY, bufferSize, data, attribSpec.usage);

					m_glArrayPack->getArray(attribNdx)->setupArray(true, attribSpec.offset, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, attribSpec.normalize, attribSpec.stride, attribSpec.instanceDivisor, nullAttribValue, isPositionAttr, attribSpec.bgraComponentOrder);
					m_rrArrayPack->getArray(attribNdx)->setupArray(true, attribSpec.offset, attribSpec.componentCount, attribSpec.inputType, attribSpec.outputType, attribSpec.normalize, attribSpec.stride, attribSpec.instanceDivisor, nullAttribValue, isPositionAttr, attribSpec.bgraComponentOrder);

					delete [] data;
					data = NULL;
				}
				catch (...)
				{
					delete [] data;
					throw;
				}
			}
		}

		// Shader program
		if (updateProgram)
		{
			m_glArrayPack->updateProgram();
			m_rrArrayPack->updateProgram();
		}

		// Draw
		try
		{
			// indices
			if (indexed)
			{
				const int		seed				= spec.hash();
				const size_t	indexElementSize	= DrawTestSpec::indexTypeSize(spec.indexType);
				const size_t	indexArraySize		= spec.indexPointerOffset + indexElementSize * elementCount;
				const char*		indexArray			= RandomArrayGenerator::generateIndices(seed, (int)elementCount, spec.indexType, spec.indexPointerOffset, indexMin, indexMax, indexBase);
				const char*		indexPointerBase	= (spec.indexStorage == DrawTestSpec::STORAGE_USER) ? (indexArray) : ((char*)DE_NULL);
				const char*		indexPointer		= indexPointerBase + spec.indexPointerOffset;

				de::UniquePtr<AttributeArray> glArray	(new AttributeArray(spec.indexStorage, *m_glesContext));
				de::UniquePtr<AttributeArray> rrArray	(new AttributeArray(spec.indexStorage, *m_refContext));

				try
				{
					glArray->data(DrawTestSpec::TARGET_ELEMENT_ARRAY, indexArraySize, indexArray, DrawTestSpec::USAGE_STATIC_DRAW);
					rrArray->data(DrawTestSpec::TARGET_ELEMENT_ARRAY, indexArraySize, indexArray, DrawTestSpec::USAGE_STATIC_DRAW);

					m_glArrayPack->render(spec.primitive, spec.drawMethod, 0, (int)primitiveElementCount, spec.indexType, indexPointer, spec.indexMin, spec.indexMax, spec.instanceCount, spec.indirectOffset, spec.baseVertex, coordScale, colorScale, glArray.get());
					m_rrArrayPack->render(spec.primitive, spec.drawMethod, 0, (int)primitiveElementCount, spec.indexType, indexPointer, spec.indexMin, spec.indexMax, spec.instanceCount, spec.indirectOffset, spec.baseVertex, coordScale, colorScale, rrArray.get());

					delete [] indexArray;
					indexArray = NULL;
				}
				catch (...)
				{
					delete [] indexArray;
					throw;
				}
			}
			else
			{
				m_glArrayPack->render(spec.primitive, spec.drawMethod, spec.first, (int)primitiveElementCount, DrawTestSpec::INDEXTYPE_LAST, DE_NULL, 0, 0, spec.instanceCount, spec.indirectOffset, 0, coordScale, colorScale, DE_NULL);
				m_testCtx.touchWatchdog();
				m_rrArrayPack->render(spec.primitive, spec.drawMethod, spec.first, (int)primitiveElementCount, DrawTestSpec::INDEXTYPE_LAST, DE_NULL, 0, 0, spec.instanceCount, spec.indirectOffset, 0, coordScale, colorScale, DE_NULL);
			}
		}
		catch (glu::Error& err)
		{
			// GL Errors are ok if the mode is not properly aligned

			const DrawTestSpec::CompatibilityTestType ctype = spec.isCompatibilityTest();

			m_testCtx.getLog() << TestLog::Message << "Got error: " << err.what() << TestLog::EndMessage;

			if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET)
				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
			else if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
			else
				throw;
		}
	}
	else if (compareStep)
	{
		if (!compare(spec.primitive))
		{
			const DrawTestSpec::CompatibilityTestType ctype = spec.isCompatibilityTest();

			if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_OFFSET)
				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned buffers.");
			else if (ctype == DrawTestSpec::COMPATIBILITY_UNALIGNED_STRIDE)
				m_result.addResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned stride.");
			else
				m_result.addResult(QP_TEST_RESULT_FAIL, "Image comparison failed.");
		}
	}
	else
	{
		DE_ASSERT(false);
		return STOP;
	}

	m_result.setTestContextResult(m_testCtx);

	m_iteration++;
	return iterateResult;
}

static bool isBlack (const tcu::RGBA& c)
{
	// ignore alpha channel
	return c.getRed() == 0 && c.getGreen() == 0 && c.getBlue() == 0;
}

static bool isEdgeTripletComponent (int c1, int c2, int c3, int renderTargetDifference)
{
	const int	roundingDifference	= 2 * renderTargetDifference; // src and dst pixels rounded to different directions
	const int	d1					= c2 - c1;
	const int	d2					= c3 - c2;
	const int	rampDiff			= de::abs(d2 - d1);

	return rampDiff > roundingDifference;
}

static bool isEdgeTriplet (const tcu::RGBA& c1, const tcu::RGBA& c2, const tcu::RGBA& c3, const tcu::IVec3& renderTargetThreshold)
{
	// black (background color) and non-black is always an edge
	{
		const bool b1 = isBlack(c1);
		const bool b2 = isBlack(c2);
		const bool b3 = isBlack(c3);

		// both pixels with coverage and pixels without coverage
		if ((b1 && b2 && b3) == false && (b1 || b2 || b3) == true)
			return true;
		// all black
		if (b1 && b2 && b3)
			return false;
		// all with coverage
		DE_ASSERT(!b1 && !b2 && !b3);
	}

	// Color is always linearly interpolated => component values change nearly linearly
	// in any constant direction on triangle hull. (df/dx ~= C).

	// Edge detection (this function) is run against the reference image
	// => no dithering to worry about

	return	isEdgeTripletComponent(c1.getRed(),		c2.getRed(),	c3.getRed(),	renderTargetThreshold.x())	||
			isEdgeTripletComponent(c1.getGreen(),	c2.getGreen(),	c3.getGreen(),	renderTargetThreshold.y())	||
			isEdgeTripletComponent(c1.getBlue(),	c2.getBlue(),	c3.getBlue(),	renderTargetThreshold.z());
}

static bool pixelNearEdge (int x, int y, const tcu::Surface& ref, const tcu::IVec3& renderTargetThreshold)
{
	// should not be called for edge pixels
	DE_ASSERT(x >= 1 && x <= ref.getWidth()-2);
	DE_ASSERT(y >= 1 && y <= ref.getHeight()-2);

	// horizontal

	for (int dy = -1; dy < 2; ++dy)
	{
		const tcu::RGBA c1 = ref.getPixel(x-1, y+dy);
		const tcu::RGBA c2 = ref.getPixel(x,   y+dy);
		const tcu::RGBA c3 = ref.getPixel(x+1, y+dy);
		if (isEdgeTriplet(c1, c2, c3, renderTargetThreshold))
			return true;
	}

	// vertical

	for (int dx = -1; dx < 2; ++dx)
	{
		const tcu::RGBA c1 = ref.getPixel(x+dx, y-1);
		const tcu::RGBA c2 = ref.getPixel(x+dx, y);
		const tcu::RGBA c3 = ref.getPixel(x+dx, y+1);
		if (isEdgeTriplet(c1, c2, c3, renderTargetThreshold))
			return true;
	}

	return false;
}

static deUint32 getVisualizationGrayscaleColor (const tcu::RGBA& c)
{
	// make triangle coverage and error pixels obvious by converting coverage to grayscale
	if (isBlack(c))
		return 0;
	else
		return 50u + (deUint32)(c.getRed() + c.getBlue() + c.getGreen()) / 8u;
}

static bool pixelNearLineIntersection (int x, int y, const tcu::Surface& target)
{
	// should not be called for edge pixels
	DE_ASSERT(x >= 1 && x <= target.getWidth()-2);
	DE_ASSERT(y >= 1 && y <= target.getHeight()-2);

	int coveredPixels = 0;

	for (int dy = -1; dy < 2; dy++)
	for (int dx = -1; dx < 2; dx++)
	{
		const bool targetCoverage = !isBlack(target.getPixel(x+dx, y+dy));
		if (targetCoverage)
		{
			++coveredPixels;

			// A single thin line cannot have more than 3 covered pixels in a 3x3 area
			if (coveredPixels >= 4)
				return true;
		}
	}

	return false;
}

static inline bool colorsEqual (const tcu::RGBA& colorA, const tcu::RGBA& colorB, const tcu::IVec3& compareThreshold)
{
	enum
	{
		TCU_RGBA_RGB_MASK = tcu::RGBA::RED_MASK | tcu::RGBA::GREEN_MASK | tcu::RGBA::BLUE_MASK
	};

	return tcu::compareThresholdMasked(colorA, colorB, tcu::RGBA(compareThreshold.x(), compareThreshold.y(), compareThreshold.z(), 0), TCU_RGBA_RGB_MASK);
}

// search 3x3 are for matching color
static bool pixelNeighborhoodContainsColor (const tcu::Surface& target, int x, int y, const tcu::RGBA& color, const tcu::IVec3& compareThreshold)
{
	// should not be called for edge pixels
	DE_ASSERT(x >= 1 && x <= target.getWidth()-2);
	DE_ASSERT(y >= 1 && y <= target.getHeight()-2);

	for (int dy = -1; dy < 2; dy++)
	for (int dx = -1; dx < 2; dx++)
	{
		const tcu::RGBA	targetCmpPixel = target.getPixel(x+dx, y+dy);
		if (colorsEqual(color, targetCmpPixel, compareThreshold))
			return true;
	}

	return false;
}

// search 3x3 are for matching coverage (coverage == (color != background color))
static bool pixelNeighborhoodContainsCoverage (const tcu::Surface& target, int x, int y, bool coverage)
{
	// should not be called for edge pixels
	DE_ASSERT(x >= 1 && x <= target.getWidth()-2);
	DE_ASSERT(y >= 1 && y <= target.getHeight()-2);

	for (int dy = -1; dy < 2; dy++)
	for (int dx = -1; dx < 2; dx++)
	{
		const bool targetCmpCoverage = !isBlack(target.getPixel(x+dx, y+dy));
		if (targetCmpCoverage == coverage)
			return true;
	}

	return false;
}

static bool edgeRelaxedImageCompare (tcu::TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::Surface& reference, const tcu::Surface& result, const tcu::IVec3& compareThreshold, const tcu::IVec3& renderTargetThreshold, int maxAllowedInvalidPixels)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());

	const tcu::IVec4				green						(0, 255, 0, 255);
	const tcu::IVec4				red							(255, 0, 0, 255);
	const int						width						= reference.getWidth();
	const int						height						= reference.getHeight();
	tcu::TextureLevel				errorMask					(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), width, height);
	const tcu::PixelBufferAccess	errorAccess					= errorMask.getAccess();
	int								numFailingPixels			= 0;

	// clear errormask edges which would otherwise be transparent

	tcu::clear(tcu::getSubregion(errorAccess, 0,			0,			width,	1),			green);
	tcu::clear(tcu::getSubregion(errorAccess, 0,			height-1,	width,	1),			green);
	tcu::clear(tcu::getSubregion(errorAccess, 0,			0,			1,		height),	green);
	tcu::clear(tcu::getSubregion(errorAccess, width-1,		0,			1,		height),	green);

	// skip edge pixels since coverage on edge cannot be verified

	for (int y = 1; y < height - 1; ++y)
	for (int x = 1; x < width - 1; ++x)
	{
		const tcu::RGBA	refPixel			= reference.getPixel(x, y);
		const tcu::RGBA	screenPixel			= result.getPixel(x, y);
		const bool		directMatch			= colorsEqual(refPixel, screenPixel, compareThreshold);
		const bool		isOkReferencePixel	= directMatch || pixelNeighborhoodContainsColor(result, x, y, refPixel, compareThreshold);			// screen image has a matching pixel nearby (~= If something is drawn on reference, it must be drawn to screen too.)
		const bool		isOkScreenPixel		= directMatch || pixelNeighborhoodContainsColor(reference, x, y, screenPixel, compareThreshold);	// reference image has a matching pixel nearby (~= If something is drawn on screen, it must be drawn to reference too.)

		if (isOkScreenPixel && isOkReferencePixel)
		{
			// pixel valid, write greenish pixels to make the result image easier to read
			const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
			errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
		}
		else if (!pixelNearEdge(x, y, reference, renderTargetThreshold))
		{
			// non-edge pixel values must be within threshold of the reference values
			errorAccess.setPixel(red, x, y);
			++numFailingPixels;
		}
		else
		{
			// we are on/near an edge, verify only coverage (coverage == not background colored)
			const bool	referenceCoverage		= !isBlack(refPixel);
			const bool	screenCoverage			= !isBlack(screenPixel);
			const bool	isOkReferenceCoverage	= pixelNeighborhoodContainsCoverage(result, x, y, referenceCoverage);	// Check reference pixel against screen pixel
			const bool	isOkScreenCoverage		= pixelNeighborhoodContainsCoverage(reference, x, y, screenCoverage);	// Check screen pixels against reference pixel

			if (isOkScreenCoverage && isOkReferenceCoverage)
			{
				// pixel valid, write greenish pixels to make the result image easier to read
				const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
				errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
			}
			else
			{
				// coverage does not match
				errorAccess.setPixel(red, x, y);
				++numFailingPixels;
			}
		}
	}

	log	<< TestLog::Message
		<< "Comparing images:\n"
		<< "\tallowed deviation in pixel positions = 1\n"
		<< "\tnumber of allowed invalid pixels = " << maxAllowedInvalidPixels << "\n"
		<< "\tnumber of invalid pixels = " << numFailingPixels
		<< TestLog::EndMessage;

	if (numFailingPixels > maxAllowedInvalidPixels)
	{
		log << TestLog::Message
			<< "Image comparison failed. Color threshold = (" << compareThreshold.x() << ", " << compareThreshold.y() << ", " << compareThreshold.z() << ")"
			<< TestLog::EndMessage
			<< TestLog::ImageSet(imageSetName, imageSetDesc)
			<< TestLog::Image("Result",		"Result",		result)
			<< TestLog::Image("Reference",	"Reference",	reference)
			<< TestLog::Image("ErrorMask",	"Error mask",	errorMask)
			<< TestLog::EndImageSet;

		return false;
	}
	else
	{
		log << TestLog::ImageSet(imageSetName, imageSetDesc)
			<< TestLog::Image("Result", "Result", result)
			<< TestLog::EndImageSet;

		return true;
	}
}

static bool intersectionRelaxedLineImageCompare (tcu::TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::Surface& reference, const tcu::Surface& result, const tcu::IVec3& compareThreshold, int maxAllowedInvalidPixels)
{
	DE_ASSERT(result.getWidth() == reference.getWidth() && result.getHeight() == reference.getHeight());

	const tcu::IVec4				green						(0, 255, 0, 255);
	const tcu::IVec4				red							(255, 0, 0, 255);
	const int						width						= reference.getWidth();
	const int						height						= reference.getHeight();
	tcu::TextureLevel				errorMask					(tcu::TextureFormat(tcu::TextureFormat::RGB, tcu::TextureFormat::UNORM_INT8), width, height);
	const tcu::PixelBufferAccess	errorAccess					= errorMask.getAccess();
	int								numFailingPixels			= 0;

	// clear errormask edges which would otherwise be transparent

	tcu::clear(tcu::getSubregion(errorAccess, 0,			0,			width,	1),			green);
	tcu::clear(tcu::getSubregion(errorAccess, 0,			height-1,	width,	1),			green);
	tcu::clear(tcu::getSubregion(errorAccess, 0,			0,			1,		height),	green);
	tcu::clear(tcu::getSubregion(errorAccess, width-1,		0,			1,		height),	green);

	// skip edge pixels since coverage on edge cannot be verified

	for (int y = 1; y < height - 1; ++y)
	for (int x = 1; x < width - 1; ++x)
	{
		const tcu::RGBA	refPixel			= reference.getPixel(x, y);
		const tcu::RGBA	screenPixel			= result.getPixel(x, y);
		const bool		directMatch			= colorsEqual(refPixel, screenPixel, compareThreshold);
		const bool		isOkScreenPixel		= directMatch || pixelNeighborhoodContainsColor(reference, x, y, screenPixel, compareThreshold);	// reference image has a matching pixel nearby (~= If something is drawn on screen, it must be drawn to reference too.)
		const bool		isOkReferencePixel	= directMatch || pixelNeighborhoodContainsColor(result, x, y, refPixel, compareThreshold);			// screen image has a matching pixel nearby (~= If something is drawn on reference, it must be drawn to screen too.)

		if (isOkScreenPixel && isOkReferencePixel)
		{
			// pixel valid, write greenish pixels to make the result image easier to read
			const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
			errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
		}
		else if (!pixelNearLineIntersection(x, y, reference) &&
				 !pixelNearLineIntersection(x, y, result))
		{
			// non-intersection pixel values must be within threshold of the reference values
			errorAccess.setPixel(red, x, y);
			++numFailingPixels;
		}
		else
		{
			// pixel is near a line intersection
			// we are on/near an edge, verify only coverage (coverage == not background colored)
			const bool	referenceCoverage		= !isBlack(refPixel);
			const bool	screenCoverage			= !isBlack(screenPixel);
			const bool	isOkScreenCoverage		= pixelNeighborhoodContainsCoverage(reference, x, y, screenCoverage);	// Check screen pixels against reference pixel
			const bool	isOkReferenceCoverage	= pixelNeighborhoodContainsCoverage(result, x, y, referenceCoverage);	// Check reference pixel against screen pixel

			if (isOkScreenCoverage && isOkReferenceCoverage)
			{
				// pixel valid, write greenish pixels to make the result image easier to read
				const deUint32 grayscaleValue = getVisualizationGrayscaleColor(screenPixel);
				errorAccess.setPixel(tcu::UVec4(grayscaleValue, 255, grayscaleValue, 255), x, y);
			}
			else
			{
				// coverage does not match
				errorAccess.setPixel(red, x, y);
				++numFailingPixels;
			}
		}
	}

	log	<< TestLog::Message
		<< "Comparing images:\n"
		<< "\tallowed deviation in pixel positions = 1\n"
		<< "\tnumber of allowed invalid pixels = " << maxAllowedInvalidPixels << "\n"
		<< "\tnumber of invalid pixels = " << numFailingPixels
		<< TestLog::EndMessage;

	if (numFailingPixels > maxAllowedInvalidPixels)
	{
		log << TestLog::Message
			<< "Image comparison failed. Color threshold = (" << compareThreshold.x() << ", " << compareThreshold.y() << ", " << compareThreshold.z() << ")"
			<< TestLog::EndMessage
			<< TestLog::ImageSet(imageSetName, imageSetDesc)
			<< TestLog::Image("Result",		"Result",		result)
			<< TestLog::Image("Reference",	"Reference",	reference)
			<< TestLog::Image("ErrorMask",	"Error mask",	errorMask)
			<< TestLog::EndImageSet;

		return false;
	}
	else
	{
		log << TestLog::ImageSet(imageSetName, imageSetDesc)
			<< TestLog::Image("Result", "Result", result)
			<< TestLog::EndImageSet;

		return true;
	}
}

bool DrawTest::compare (gls::DrawTestSpec::Primitive primitiveType)
{
	const tcu::Surface&	ref		= m_rrArrayPack->getSurface();
	const tcu::Surface&	screen	= m_glArrayPack->getSurface();

	if (m_renderCtx.getRenderTarget().getNumSamples() > 1)
	{
		// \todo [mika] Improve compare when using multisampling
		m_testCtx.getLog() << tcu::TestLog::Message << "Warning: Comparision of result from multisample render targets are not as stricts as without multisampling. Might produce false positives!" << tcu::TestLog::EndMessage;
		return tcu::fuzzyCompare(m_testCtx.getLog(), "Compare Results", "Compare Results", ref.getAccess(), screen.getAccess(), 0.3f, tcu::COMPARE_LOG_RESULT);
	}
	else
	{
		const PrimitiveClass	primitiveClass							= getDrawPrimitiveClass(primitiveType);
		const int				maxAllowedInvalidPixelsWithPoints		= 0;	//!< points are unlikely to have overlapping fragments
		const int				maxAllowedInvalidPixelsWithLines		= 5;	//!< line are allowed to have a few bad pixels
		const int				maxAllowedInvalidPixelsWithTriangles	= 10;

		switch (primitiveClass)
		{
			case PRIMITIVECLASS_POINT:
			{
				// Point are extremely unlikely to have overlapping regions, don't allow any no extra / missing pixels
				return tcu::intThresholdPositionDeviationErrorThresholdCompare(m_testCtx.getLog(),
																			   "CompareResult",
																			   "Result of rendering",
																			   ref.getAccess(),
																			   screen.getAccess(),
																			   tcu::UVec4(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue, 256),
																			   tcu::IVec3(1, 1, 0),					//!< 3x3 search kernel
																			   true,								//!< relax comparison on the image boundary
																			   maxAllowedInvalidPixelsWithPoints,	//!< error threshold
																			   tcu::COMPARE_LOG_RESULT);
			}

			case PRIMITIVECLASS_LINE:
			{
				// Lines can potentially have a large number of overlapping pixels. Pixel comparison may potentially produce
				// false negatives in such pixels if for example the pixel in question is overdrawn by another line in the
				// reference image but not in the resultin image. Relax comparison near line intersection points (areas) and
				// compare only coverage, not color, in such pixels
				return intersectionRelaxedLineImageCompare(m_testCtx.getLog(),
														   "CompareResult",
														   "Result of rendering",
														   ref,
														   screen,
														   tcu::IVec3(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue),
														   maxAllowedInvalidPixelsWithLines);
			}

			case PRIMITIVECLASS_TRIANGLE:
			{
				// Triangles are likely to partially or fully overlap. Pixel difference comparison is fragile in pixels
				// where there could be potential overlapping since the  pixels might be covered by one triangle in the
				// reference image and by the other in the result image. Relax comparsion near primitive edges and
				// compare only coverage, not color, in such pixels.
				const tcu::IVec3	renderTargetThreshold					= m_renderCtx.getRenderTarget().getPixelFormat().getColorThreshold().toIVec().xyz();

				return edgeRelaxedImageCompare(m_testCtx.getLog(),
											   "CompareResult",
											   "Result of rendering",
											   ref,
											   screen,
											   tcu::IVec3(m_maxDiffRed, m_maxDiffGreen, m_maxDiffBlue),
											   renderTargetThreshold,
											   maxAllowedInvalidPixelsWithTriangles);
			}

			default:
				DE_ASSERT(false);
				return false;
		}
	}
}

float DrawTest::getCoordScale (const DrawTestSpec& spec) const
{
	float maxValue = 1.0f;

	for (int arrayNdx = 0; arrayNdx < (int)spec.attribs.size(); arrayNdx++)
	{
		DrawTestSpec::AttributeSpec attribSpec		= spec.attribs[arrayNdx];
		const bool					isPositionAttr	= (arrayNdx == 0) || (attribSpec.additionalPositionAttribute);
		float						attrMaxValue	= 0;

		if (!isPositionAttr)
			continue;

		if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
		{
			if (attribSpec.normalize)
				attrMaxValue += 1.0f;
			else
				attrMaxValue += 1024.0f;
		}
		else if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10)
		{
			if (attribSpec.normalize)
				attrMaxValue += 1.0f;
			else
				attrMaxValue += 512.0f;
		}
		else
		{
			const float max = GLValue::getMaxValue(attribSpec.inputType).toFloat();

			attrMaxValue += (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType)) ? (1.0f) : (max * 1.1f);
		}

		if (attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC4
			|| attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC4
			|| attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC3 || attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC4)
				attrMaxValue *= 2;

		maxValue += attrMaxValue;
	}

	return 1.0f / maxValue;
}

float DrawTest::getColorScale (const DrawTestSpec& spec) const
{
	float colorScale = 1.0f;

	for (int arrayNdx = 1; arrayNdx < (int)spec.attribs.size(); arrayNdx++)
	{
		DrawTestSpec::AttributeSpec attribSpec		= spec.attribs[arrayNdx];
		const bool					isPositionAttr	= (arrayNdx == 0) || (attribSpec.additionalPositionAttribute);

		if (isPositionAttr)
			continue;

		if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_UNSIGNED_INT_2_10_10_10)
		{
			if (!attribSpec.normalize)
				colorScale *= 1.0f / 1024.0f;
		}
		else if (attribSpec.inputType == DrawTestSpec::INPUTTYPE_INT_2_10_10_10)
		{
			if (!attribSpec.normalize)
				colorScale *= 1.0f / 512.0f;
		}
		else
		{
			const float max = GLValue::getMaxValue(attribSpec.inputType).toFloat();

			colorScale *= (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType) ? 1.0f : float(1.0 / double(max)));
			if (attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_VEC4 ||
				attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_UVEC4 ||
				attribSpec.outputType == DrawTestSpec::OUTPUTTYPE_IVEC4)
				colorScale *= (attribSpec.normalize && !inputTypeIsFloatType(attribSpec.inputType) ? 1.0f : float(1.0 / double(max)));
		}
	}

	return colorScale;
}

} // gls
} // deqp
